{
  $Project$
  $Workfile$
  $Revision$
  $DateUTC$
  $Id$

  This file is part of the Indy (Internet Direct) project, and is offered
  under the dual-licensing agreement described on the Indy website.
  (http://www.indyproject.org/)

  Copyright:
   (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
{
  $Log$
}
{
  2003-11-Jul:
    Original author: Sergio Perry
    Matthew Elzer - bug fixes & modifications
  2013-26-Feb:
    Customization by ALE for PRIVMSG  ACTION
}

unit cicidirc;

{
  Based on TIRCClient component by Steve Williams (stevewilliams@kromestudios.com)
  ported to Indy by Daaron Dwyer (ddwyer@ncic.com)
}

{ Based on RFC 2812 }

interface

{$i IdCompilerDefines.inc}

uses
  Classes,
  IdAssignedNumbers, IdContext, IdCmdTCPClient, IdCommandHandlers,
  IdIOHandler, IdGlobal, IdException;

type
  TCICIdIRC = class;

  TCICIdIRCUserMode = (amAway, amInvisible, amWallops, amRestricted, amOperator, amLocalOperator, amReceiveServerNotices);
  TCICIdIRCUserModes = set of TCICIdIRCUserMode;

  TCICIdIRCStat = (stServerConnectionsList, stCommandUsageCount, stOperatorList, stUpTime);

  { -WELCOME- }
  TCICIdIRCServerMsgEvent = procedure(ASender: TIdContext; const AMsg: String) of object;
  TCICIdIRCMyInfoEvent = procedure(ASender: TIdContext; const AServer, AVersion, AUserModes, AChanModes, AExtra: String) of object;
  TCICIdIRCBounceEvent = procedure(ASender: TIdContext; const AHost: String; APort: Integer; const AInfo: String) of object;
  TCICIdIRCISupportEvent = procedure(ASender: TIdContext; AParameters: TStrings) of object;
  { -PING- }
  TCICIdIRCPingPongEvent = procedure(ASender: TIdContext) of object;
  { -MESSAGE- }
  TCICIdIRCPrivMessageEvent = procedure(ASender: TIdContext; const ANickname, AHost, ATarget, AMessage: String) of object;
  { -NOTICE- }
  TCICIdIRCNoticeEvent = procedure(ASender: TIdContext; const ANickname, AHost, ATarget, ANotice: String) of object;
  { -REHASH- }
  TCICIdIRCRehashEvent = procedure(ASender: TIdContext; const ANickname, AHost: String) of object;
  { -SUMMON- }
  TCICIdIRCSummonEvent = procedure(ASender: TIdContext; const ANickname, AHost: String) of object;
  { -WALLOPS- }
  TCICIdIRCWallopsEvent = procedure(ASender: TIdContext; const ANickname, AHost, AMessage: String) of object;
  { -ISON- }
  TCICIdIRCIsOnIRCEvent = procedure(ASender: TIdContext; const ANickname, AHost: String) of object;
  { -AWAY- }
  TCICIdIRCAwayEvent = procedure(ASender: TIdContext; const ANickname, AHost, AAwayMessage: String; UserAway: Boolean) of object;
  { -JOIN- }
  TCICIdIRCJoinEvent = procedure(ASender: TIdContext; const ANickname, AHost, AChannel: String) of object;
  { -PART- }
  TCICIdIRCPartEvent = procedure(ASender: TIdContext; const ANickname, AHost, AChannel, APartMessage: String) of object;
  { -TOPIC- }
  TCICIdIRCTopicEvent = procedure(ASender: TIdContext; const ANickname, AHost, AChannel, ATopic: String) of object;
  { -KICK- }
  TCICIdIRCKickEvent = procedure(ASender: TIdContext; const ANickname, AHost, AChannel, ATarget, AReason: String) of object;
  { -MOTD- }
  TCICIdIRCMOTDEvent = procedure(ASender: TIdContext; AMOTD: TStrings) of object;
  { -TRACE- }
  TCICIdIRCServerTraceEvent = procedure(ASender: TIdContext; ATraceInfo: TStrings) of object;
  { -OPER- }
  TCICIdIRCOpEvent = procedure(ASender: TIdContext; const ANickname, AChannel, AHost: String) of object;
  { -INV- }
  TCICIdIRCInvitingEvent = procedure(ASender: TIdContext; const ANickname, AHost: String) of object;
  TCICIdIRCInviteEvent = procedure(ASender: TIdContext; const ANickname, AHost, ATarget, AChannel: String) of object;
  { -LIST- }
  TCICIdIRCChanBANListEvent = procedure(ASender: TIdContext; const AChannel: String; ABanList: TStrings) of object;
  TCICIdIRCChanEXCListEvent = procedure(ASender: TIdContext; const AChannel: String; AExceptList: TStrings) of object;
  TCICIdIRCChanINVListEvent = procedure(ASender: TIdContext; const AChannel: String; AInviteList: TStrings) of object;
  TCICIdIRCServerListEvent = procedure(ASender: TIdContext; AServerList: TStrings) of object;
  TCICIdIRCNickListEvent = procedure(ASender: TIdContext; const AChannel: String; ANicknameList: TStrings) of object;
  { -STATS- }
  TCICIdIRCServerUsersEvent = procedure(ASender: TIdContext; AUsers: TStrings) of object;
  TCICIdIRCServerStatsEvent = procedure(ASender: TIdContext; AStatus: TStrings) of object;
  TCICIdIRCKnownServerNamesEvent = procedure(ASender: TIdContext; AKnownServers: TStrings) of object;
  { -INFO- }
  TCICIdIRCAdminInfoRecvEvent = procedure(ASender: TIdContext; AAdminInfo: TStrings) of object;
  TCICIdIRCUserInfoRecvEvent = procedure(ASender: TIdContext; const AUserInfo: String) of object;
  { -WHO- }
  TCICIdIRCWhoEvent = procedure(ASender: TIdContext; AWhoResults: TStrings) of object;
  TCICIdIRCWhoIsEvent = procedure(ASender: TIdContext; AWhoIsResults: TStrings) of object;
  TCICIdIRCWhoWasEvent = procedure(ASender: TIdContext; AWhoWasResults: TStrings) of object;
  { Mode }
  TCICIdIRCChanModeEvent = procedure(ASender: TIdContext; const ANickname, AHost, AChannel, AMode, AParams: String) of object;
  TCICIdIRCUserModeEvent = procedure(ASender: TIdContext; const ANickname, AHost, AMode: String) of object;
  { -CTCP- }
  TCICIdIRCCTCPQueryEvent = function(ASender: TIdContext; const ANickname, AHost, ATarget, ACommand, AParams: String):boolean of object;
  TCICIdIRCCTCPReplyEvent = procedure(ASender: TIdContext; const ANickname, AHost, ATarget, ACommand, AParams: String) of object;
  { -DCC- }
  TCICIdIRCDCCChatEvent = procedure(ASender: TIdContext; const ANickname, AHost: String; APort: Integer) of object;
  TCICIdIRCDCCSendEvent = procedure(ASender: TIdContext; const ANickname, AHost, AFilename: String; APort: TIdPort; AFileSize: Int64) of object;
  TCICIdIRCDCCResumeEvent = procedure(ASender: TIdContext; const ANickname, AHost, AFilename: String; APort: TIdPort;  AFilePos: Int64) of object;
  TCICIdIRCDCCAcceptEvent = procedure(ASender: TIdContext; const ANickname, AHost, AFilename: String; APort: TIdPort; AFilePos: Int64) of object;
  { -Errors- }
  TCICIdIRCServerErrorEvent = procedure(ASender: TIdContext; AErrorCode: Integer; const AErrorMessage: String) of object;
  TCICIdIRCNickErrorEvent = procedure(ASender: TIdContext; AError: Integer) of object;
  TCICIdIRCKillErrorEvent = procedure(ASender: TIdContext) of object;
  { Other }
  TCICIdIRCNicknameChangedEvent = procedure(ASender: TIdContext; const AOldNickname, AHost, ANewNickname: String) of object;
  TCICIdIRCKillEvent = procedure(ASender: TIdContext; const ANickname, AHost, ATargetNickname, AReason: String) of object;
  TCICIdIRCQuitEvent = procedure(ASender: TIdContext; const ANickname, AHost, AReason: String) of object;
  TCICIdIRCSvrQuitEvent = procedure(ASender: TIdContext; const ANickname, AHost, AServer, AReason: String) of object;
  TCICIdIRCSvrTimeEvent = procedure(ASender: TIdContext; const AHost, ATime: String) of object;
  TCICIdIRCServiceEvent = procedure(ASender: TIdContext) of object;
  TCICIdIRCSvrVersionEvent = procedure(ASender: TIdContext; const AVersion, AHost, AComments: String) of object;
  TCICIdIRCRawEvent = procedure(ASender: TIdContext; AIn: Boolean; const AMessage: String) of object;

  EIdIRCError = class(EIdException);

  TCICIdIRCReplies = class(TPersistent)
  protected
    FFinger: String;
    FVersion: String;
    FUserInfo: String;
    FClientInfo: String;
  public
    constructor Create;
    procedure Assign(Source: TPersistent); override;
  published
    property Finger: String read FFinger write FFinger;
    property Version: String read FVersion write FVersion;
    property UserInfo: String read FUserInfo write FUserInfo;
    property ClientInfo: String read FClientInfo write FClientInfo;
  end;

  TCICIdIRC = class(TIdCmdTCPClient)
  protected
    FNickname: String;
    FAltNickname: String;
    FAltNickUsed: Boolean;
    //
    FUsername: String;
    FRealName: String;
    FPassword: String;
    FUserMode: TCICIdIRCUserModes;
    FUserAway: Boolean;
    FReplies: TCICIdIRCReplies;
    //
    FSenderNick: String;
    FSenderHost: String;
    //
    FBans: TStrings;
    FExcepts: TStrings;
    FInvites: TStrings;
    FLinks: TStrings;
    FMotd: TStrings;
    FNames: TStrings;
    FWho: TStrings;
    FWhoIs: TStrings;
    FWhoWas: TStrings;
    FSvrList: TStrings;
    FUsers: TStrings;
    //
    FOnSWelcome: TCICIdIRCServerMsgEvent;
    FOnYourHost: TCICIdIRCServerMsgEvent;
    FOnSCreated: TCICIdIRCServerMsgEvent;
    FOnMyInfo: TCICIdIRCMyInfoEvent;
    FOnBounce: TCICIdIRCBounceEvent;
    FOnISupport: TCICIdIRCISupportEvent;
    FOnSError: TCICIdIRCServerMsgEvent;
    FOnPingPong: TCICIdIRCPingPongEvent;
    FOnPrivMessage: TCICIdIRCPrivMessageEvent;
    FOnNotice: TCICIdIRCNoticeEvent;
    FOnRehash: TCICIdIRCRehashEvent;
    FOnSummon: TCICIdIRCSummonEvent;
    FOnWallops: TCICIdIRCWallopsEvent;
    FOnIsOnIRC: TCICIdIRCIsOnIRCEvent;
    FOnAway: TCICIdIRCAwayEvent;
    FOnJoin: TCICIdIRCJoinEvent;
    FOnPart: TCICIdIRCPartEvent;
    FOnTopic: TCICIdIRCTopicEvent;
    FOnKick: TCICIdIRCKickEvent;
    FOnMOTD: TCICIdIRCMOTDEvent;
    FOnTrace: TCICIdIRCServerTraceEvent;
    FOnOp: TCICIdIRCOpEvent;
    FOnInviting: TCICIdIRCInvitingEvent;
    FOnInvite: TCICIdIRCInviteEvent;
    FOnBANList: TCICIdIRCChanBANListEvent;
    FOnEXCList: TCICIdIRCChanEXCListEvent;
    FOnINVList: TCICIdIRCChanINVListEvent;
    FOnSvrList: TCICIdIRCServerListEvent;
    FOnNickList: TCICIdIRCNickListEvent;
    FOnSvrUsers: TCICIdIRCServerUsersEvent;
    FOnSvrStats: TCICIdIRCServerStatsEvent;
    FOnKnownSvrs: TCICIdIRCKnownServerNamesEvent;
    FOnAdminInfo: TCICIdIRCAdminInfoRecvEvent;
    FOnUserInfo: TCICIdIRCUserInfoRecvEvent;
    FOnWho: TCICIdIRCWhoEvent;
    FOnWhoIs: TCICIdIRCWhoIsEvent;
    FOnWhoWas: TCICIdIRCWhoWasEvent;
    FOnChanMode: TCICIdIRCChanModeEvent;
    FOnUserMode: TCICIdIRCUserModeEvent;
    FOnCTCPQry: TCICIdIRCCTCPQueryEvent;
    FOnCTCPRep: TCICIdIRCCTCPReplyEvent;
    FOnDCCChat: TCICIdIRCDCCChatEvent;
    FOnDCCSend: TCICIdIRCDCCSendEvent;
    FOnDCCResume: TCICIdIRCDCCResumeEvent;
    FOnDCCAccept: TCICIdIRCDCCAcceptEvent;
    FOnServerError: TCICIdIRCServerErrorEvent;
    FOnNickError: TCICIdIRCNickErrorEvent;
    FOnKillError: TCICIdIRCKillErrorEvent;
    FOnNickChange: TCICIdIRCNicknameChangedEvent;
    FOnKill: TCICIdIRCKillEvent;
    FOnQuit: TCICIdIRCQuitEvent;
    FOnSvrQuit: TCICIdIRCSvrQuitEvent;
    FOnSvrTime: TCICIdIRCSvrTimeEvent;
    FOnService: TCICIdIRCServiceEvent;
    FOnSvrVersion: TCICIdIRCSvrVersionEvent;
    FOnRaw: TCICIdIRCRawEvent;
    //
    function GetUsedNickname: String;
    procedure SetNickname(const AValue: String);
    procedure SetUsername(const AValue: String);
    procedure SeTCICIdIRCUserMode(AValue: TCICIdIRCUserModes);
    procedure SeTCICIdIRCReplies(AValue: TCICIdIRCReplies);
    function GetUserMode: String;
    procedure ParseDCC(AContext: TIdContext; const ADCC: String);
    //Command handlers
    procedure DoBeforeCmd(ASender: TIdCommandHandlers; var AData: string; AContext: TIdContext);
    procedure DoReplyUnknownCommand(AContext: TIdContext; ALine: string); override;
    procedure DoBounce(ASender: TIdCommand; ALegacy: Boolean);
    procedure CommandPRIVMSG(ASender: TIdCommand);
    procedure CommandNOTICE(ASender: TIdCommand);
    procedure CommandJOIN(ASender: TIdCommand);
    procedure CommandPART(ASender: TIdCommand);
    procedure CommandKICK(ASender: TIdCommand);
    procedure CommandMODE(ASender: TIdCommand);
    procedure CommandNICK(ASender: TIdCommand);
    procedure CommandQUIT(ASender: TIdCommand);
    procedure CommandSQUIT(ASender: TIdCommand);
    procedure CommandINVITE(ASender: TIdCommand);
    procedure CommandKILL(ASender: TIdCommand);
    procedure CommandPING(ASender: TIdCommand);
    procedure CommandERROR(ASender: TIdCommand);
    procedure CommandWALLOPS(ASender: TIdCommand);
    procedure CommandTOPIC(ASender: TIdCommand);
    procedure CommandWELCOME(ASender: TIdCommand);
    procedure CommandYOURHOST(ASender: TIdCommand);
    procedure CommandCREATED(ASender: TIdCommand);
    procedure CommandMYINFO(ASender: TIdCommand);
    procedure CommandISUPPORT(ASender: TIdCommand);
    procedure CommandBOUNCE(ASender: TIdCommand);
    procedure CommandUSERHOST(ASender: TIdCommand);
    procedure CommandISON(ASender: TIdCommand);
    procedure CommandWHOIS(ASender: TIdCommand);
    procedure CommandENDOFWHOIS(ASender: TIdCommand);
    procedure CommandWHOWAS(ASender: TIdCommand);
    procedure CommandENDOFWHOWAS(ASender: TIdCommand);
    procedure CommandLISTSTART(ASender: TIdCommand);
    procedure CommandLIST(ASender: TIdCommand);
    procedure CommandLISTEND(ASender: TIdCommand);
    procedure CommandAWAY(ASender: TIdCommand);
    procedure CommandINVITING(ASender: TIdCommand);
    procedure CommandSUMMONING(ASender: TIdCommand);
    procedure CommandINVITELIST(ASender: TIdCommand);
    procedure CommandENDOFINVITELIST(ASender: TIdCommand);
    procedure CommandEXCEPTLIST(ASender: TIdCommand);
    procedure CommandENDOFEXCEPTLIST(ASender: TIdCommand);
    procedure CommandWHOREPLY(ASender: TIdCommand);
    procedure CommandENDOFWHO(ASender: TIdCommand);
    procedure CommandNAMEREPLY(ASender: TIdCommand);
    procedure CommandENDOFNAMES(ASender: TIdCommand);
    procedure CommandLINKS(ASender: TIdCommand);
    procedure CommandENDOFLINKS(ASender: TIdCommand);
    procedure CommandBANLIST(ASender: TIdCommand);
    procedure CommandENDOFBANLIST(ASender: TIdCommand);
    procedure CommandINFO(ASender: TIdCommand);
    procedure CommandENDOFINFO(ASender: TIdCommand);
    procedure CommandMOTD(ASender: TIdCommand);
    procedure CommandENDOFMOTD(ASender: TIdCommand);
    procedure CommandREHASHING(ASender: TIdCommand);
    procedure CommandUSERSSTART(ASender: TIdCommand);
    procedure CommandUSERS(ASender: TIdCommand);
    procedure CommandENDOFUSERS(ASender: TIdCommand);
    procedure CommandENDOFSTATS(ASender: TIdCommand);
    procedure CommandSERVLIST(ASender: TIdCommand);
    procedure CommandSERVLISTEND(ASender: TIdCommand);
    procedure CommandTIME(ASender: TIdCommand);
    procedure CommandSERVICE(ASender: TIdCommand);
    procedure CommandVERSION(ASender: TIdCommand);
    procedure CommandCHANMODE(ASender: TIdCommand);
    procedure CommandOPER(ASender: TIdCommand);
    procedure CommandNICKINUSE(ASender: TIdCommand);
    //
    procedure AssignIRCClientCommands;
    function GetCmdHandlerClass: TIdCommandHandlerClass; override;
    procedure SetIOHandler(AValue: TIdIOHandler); override;
    procedure InitComponent; override;
  public
    destructor Destroy; override;
    //
    procedure Connect; override;
    procedure Disconnect(const AReason: String = ''); reintroduce;
    //
    function IsChannel(const AChannel: String): Boolean;
    function IsOp(const ANickname: String): Boolean;
    function IsVoice(const ANickname: String): Boolean;
    procedure Raw(const ALine: String);
    procedure Say(const ATarget, AMsg: String);
    procedure Notice(const ATarget, AMsg: String);
    procedure Action(const ATarget, AMsg: String);
    procedure CTCPQuery(const ATarget, ACommand, AParameters: String);
    procedure CTCPReply(const ATarget, ACTCP, AReply: String);
    procedure Join(const AChannel: String; const AKey: String ='');
    procedure Part(const AChannel: String; const AReason: String = '');
    procedure Kick(const AChannel, ANickname: String; const AReason: String = '');
    procedure SetChannelMode(const AChannel, AMode: String; const AParams: String = '');
    procedure SetUserMode(const ANickname, AMode: String);
    procedure GetChannelTopic(const AChannel: String);
    procedure SetChannelTopic(const AChannel, ATopic: String);
    procedure SetAway(const AMsg: String);
    procedure Op(const AChannel, ANickname: String);
    procedure Deop(const AChannel, ANickname: String);
    procedure Voice(const AChannel, ANickname: String);
    procedure Devoice(const AChannel, ANickname: String);
    procedure Ban(const AChannel, AHostmask: String);
    procedure Unban(const AChannel, AHostmask: String);
    procedure RegisterService(const ANickname, ADistribution, AInfo: String; AType: Integer);
    procedure ListChannelNicknames(const AChannel: String; const ATarget: String = '');
    procedure ListChannel(const AChannel: String; const ATarget: String = '');
    procedure Invite(const ANickname, AChannel: String);
    procedure GetMessageOfTheDay(const ATarget: String = '');
    procedure GetNetworkStatus(const AHostMask: String = ''; const ATarget: String = '');
    procedure GetServerVersion(const ATarget: String = '');
    procedure GetServerStatus(AQuery: TCICIdIRCStat; const ATarget: String = '');
    procedure ListKnownServerNames(const ARemoteHost: String = ''; const AHostMask: String = '');
    procedure QueryServerTime(const ATarget: String = '');
    procedure RequestServerConnect(const ATargetHost: String; APort: Integer; const ARemoteHost: String = '');
    procedure TraceServer(const ATarget: String = '');
    procedure GetAdminInfo(const ATarget: String = '');
    procedure GetServerInfo(const ATarget: String = '');
    procedure ListNetworkServices(const AHostMask: String = ''; const AType: String = '');
    procedure QueryService(const AServiceName, AMessage: String);
    procedure Who(const AMask: String; AOnlyAdmins: Boolean);
    procedure WhoIs(const AMask: String; const ATarget: String = '');
    procedure WhoWas(const ANickname: String; ACount: Integer = -1; const ATarget: String = '');
    procedure Kill(const ANickname, AComment: String);
    procedure Ping(const AServer1: String; const AServer2: String = '');
    procedure Pong(const AServer1: String; const AServer2: String = '');
    procedure Error(const AMessage: String);
    procedure ReHash;
    procedure Die;
    procedure Restart;
    procedure Summon(const ANickname: String; const ATarget: String = ''; const AChannel: String = '');
    procedure ListServerUsers(const ATarget: String = '');
    procedure SayWALLOPS(const AMessage: String);
    procedure GetUserInfo(const ANickname: String);
    procedure GetUsersInfo(const ANicknames: array of String);
    procedure IsOnIRC(const ANickname: String); overload;
    procedure IsOnIRC(const ANicknames: array of String); overload;
    procedure BecomeOp(const ANickname, APassword: String);
    procedure SQuit(const AHost, AComment: String);
    procedure SetChannelLimit(const AChannel: String; ALimit: Integer);
    procedure SetChannelKey(const AChannel, AKey: String);
    //
    property Away: Boolean read FUserAway;
  published
    property Nickname: String read FNickname write SetNickname;
    property AltNickname: String read FAltNickname write FAltNickname;
    property UsedNickname: String read GetUsedNickname; // returns Nickname or AltNickname
    property Username: String read FUsername write SetUsername;
    property RealName: String read FRealName write FRealName;
    property Password: String read FPassword write FPassword;
    property Port default IdPORT_IRC;
    property Replies: TCICIdIRCReplies read FReplies write SeTCICIdIRCReplies;
    property UserMode: TCICIdIRCUserModes read FUserMode write SeTCICIdIRCUserMode;
    { Events }
    property OnServerWelcome: TCICIdIRCServerMsgEvent read FOnSWelcome write FOnSWelcome;
    property OnYourHost: TCICIdIRCServerMsgEvent read FOnYourHost write FOnYourHost;
    property OnServerCreated: TCICIdIRCServerMsgEvent read FOnSCreated write FOnSCreated;
    property OnMyInfo: TCICIdIRCMyInfoEvent read FOnMyInfo write FOnMyInfo;
    property OnBounce: TCICIdIRCBounceEvent read FOnBounce write FOnBounce;
    property OnISupport: TCICIdIRCISupportEvent read FOnISupport write FOnISupport;
    property OnPingPong: TCICIdIRCPingPongEvent read FOnPingPong write FOnPingPong;
    property OnPrivateMessage: TCICIdIRCPrivMessageEvent read FOnPrivMessage write FOnPrivMessage;
    property OnNotice: TCICIdIRCNoticeEvent read FOnNotice write FOnNotice;
    property OnRehash: TCICIdIRCRehashEvent read FOnRehash write FOnRehash;
    property OnSummon: TCICIdIRCSummonEvent read FOnSummon write FOnSummon;
    property OnWallops: TCICIdIRCWallopsEvent read FOnWallops write FOnWallops;
    property OnIsOnIRC: TCICIdIRCIsOnIRCEvent read FOnIsOnIRC write FOnIsOnIRC;
    property OnAway: TCICIdIRCAwayEvent read FOnAway write FOnAway;
    property OnJoin: TCICIdIRCJoinEvent read FOnJoin write FOnJoin;
    property OnPart: TCICIdIRCPartEvent read FOnPart write FOnPart;
    property OnTopic: TCICIdIRCTopicEvent read FOnTopic write FOnTopic;
    property OnKick: TCICIdIRCKickEvent read FOnKick write FOnKick;
    property OnMOTD: TCICIdIRCMOTDEvent read FOnMOTD write FOnMOTD;
    property OnTrace: TCICIdIRCServerTraceEvent read FOnTrace write FOnTrace;
    property OnOp: TCICIdIRCOpEvent read FOnOp write FOnOp;
    property OnInviting: TCICIdIRCInvitingEvent read FOnInviting write FOnInviting;
    property OnInvite: TCICIdIRCInviteEvent read FOnInvite write FOnInvite;
    property OnBanListReceived: TCICIdIRCChanBANListEvent read FOnBANList write FOnBANList;
    property OnExceptionListReceived: TCICIdIRCChanEXCListEvent read FOnEXCList write FOnEXCList;
    property OnInvitationListReceived: TCICIdIRCChanINVListEvent read FOnINVList write FOnINVList;
    property OnServerListReceived: TCICIdIRCServerListEvent read FOnSvrList write FOnSvrList;
    property OnNicknamesListReceived: TCICIdIRCNickListEvent read FOnNickList write FOnNickList;
    property OnServerUsersListReceived: TCICIdIRCServerUsersEvent read FOnSvrUsers write FOnSvrUsers;
    property OnServerStatsReceived: TCICIdIRCServerStatsEvent read FOnSvrStats write FOnSvrStats;
    property OnKnownServersListReceived: TCICIdIRCKnownServerNamesEvent read FOnKnownSvrs write FOnKnownSvrs;
    property OnAdminInfoReceived: TCICIdIRCAdminInfoRecvEvent read FOnAdminInfo write FOnAdminInfo;
    property OnUserInfoReceived: TCICIdIRCUserInfoRecvEvent read FOnUserInfo write FOnUserInfo;
    property OnWho: TCICIdIRCWhoEvent read FOnWho write FOnWho;
    property OnWhoIs: TCICIdIRCWhoIsEvent read FOnWhoIs write FOnWhoIs;
    property OnWhoWas: TCICIdIRCWhoWasEvent read FOnWhoWas write FOnWhoWas;
    property OnChannelMode: TCICIdIRCChanModeEvent read FOnChanMode write FOnChanMode;
    property OnUserMode: TCICIdIRCUserModeEvent read FOnUserMode write FOnUserMode;
    property OnCTCPQuery: TCICIdIRCCTCPQueryEvent read FOnCTCPQry write FOnCTCPQry;
    property OnCTCPReply: TCICIdIRCCTCPReplyEvent read FOnCTCPRep write FOnCTCPRep;
    property OnDCCChat: TCICIdIRCDCCChatEvent read FOnDCCChat write FOnDCCChat;
    property OnDCCSend: TCICIdIRCDCCSendEvent read FOnDCCSend write FOnDCCSend;
    property OnDCCResume: TCICIdIRCDCCResumeEvent read FOnDCCResume write FOnDCCResume;
    property OnDCCAccept: TCICIdIRCDCCAcceptEvent read FOnDCCAccept write FOnDCCAccept;
    property OnServerError: TCICIdIRCServerErrorEvent read FOnServerError write FOnServerError;
    property OnNicknameError: TCICIdIRCNickErrorEvent read FOnNickError write FOnNickError;
    property OnKillError: TCICIdIRCKillErrorEvent read FOnKillError write FOnKillError;
    property OnNicknameChange: TCICIdIRCNicknameChangedEvent read FOnNickChange write FOnNickChange;
    property OnKill: TCICIdIRCKillEvent read FOnKill write FOnKill;
    property OnQuit: TCICIdIRCQuitEvent read FOnQuit write FOnQuit;
    property OnServerQuit: TCICIdIRCSvrQuitEvent read FOnSvrQuit write FOnSvrQuit;
    property OnServerTime: TCICIdIRCSvrTimeEvent read FOnSvrTime write FOnSvrTime;
    property OnService: TCICIdIRCServiceEvent read FOnService write FOnService;
    property OnServerVersion: TCICIdIRCSvrVersionEvent read FOnSvrVersion write FOnSvrVersion;
    property OnRaw: TCICIdIRCRawEvent read FOnRaw write FOnRaw;
  end;

implementation

uses
  IdGlobalProtocols, IdResourceStringsProtocols, IdSSL,
  IdStack, IdBaseComponent, SysUtils;

const
  IdIRCCTCP: array[0..10] of String = ('ACTION', 'SOUND', 'PING', 'FINGER', {do not localize}
    'USERINFO', 'VERSION', 'CLIENTINFO', 'TIME', 'ERROR', 'DCC', 'SED');  {do not localize}

  MQuote = #16;
  XDelim = #1;
  XQuote = #92;

{ TCICIdIRCReplies }

constructor TCICIdIRCReplies.Create;
begin
  inherited Create;
  //
end;

procedure TCICIdIRCReplies.Assign(Source: TPersistent);
var
  LSource: TCICIdIRCReplies;
begin
  if Source is TCICIdIRCReplies then
  begin
    LSource := TCICIdIRCReplies(Source);
    FFinger := LSource.Finger;
    FFinger := LSource.Finger;
    FVersion := LSource.Version;
    FUserInfo := LSource.UserInfo;
    FClientInfo := LSource.ClientInfo;
  end else begin
    inherited Assign(Source);
  end;
end;

{ TCICIdIRC }

// RLebeau 1/7/2010: SysUtils.TrimLeft() removes all characters < #32, but
// CTC requires character #1, so don't remove that character when parsing
// IRC parameters in FetchIRCParam()...
//
function IRCTrimLeft(const S: string): string;
var
  I, L: Integer;
begin
  L := Length(S);
  I := 1;
  while (I <= L) and (S[I] <= ' ') and (S[I] <> XDelim) do begin
    Inc(I);
  end;
  Result := Copy(S, I, Maxint);
end;

function FetchIRCParam(var S: String): String;
var
  LTmp: String;
begin
  LTmp := IRCTrimLeft(S);
  if TextStartsWith(LTmp, ':') then
  begin
    Result := Copy(LTmp, 2, MaxInt);
    S := '';
  end else
  begin
    Result := Fetch(LTmp, ' ');
    S := IRCTrimLeft(LTmp);
  end;
end;

function IRCQuote(const S: String): String;
begin
  // IMPORTANT! MQuote needs to be the first character in the replacement
  // list, otherwise it will end up being double-escaped if the other
  // character get replaced, which will produce the wrong output!!
  Result := StringsReplace(S, [MQuote, #0, LF, CR], [MQuote+MQuote, MQuote+'0', MQuote+'n', MQuote+'r']);
end;

function IRCUnquote(const S: String): String;
var
  I, L: Integer;
begin
  Result := S;
  L := Length(Result);
  I := 1;
  repeat
    I := PosIdx(MQuote, Result, I);
    if I = 0 then begin
      Break;
    end;
    IdDelete(Result, I, 1);
    Dec(L);
    if I > L then begin
      Break;
    end;
    case Result[I] of
      '0': Result[I] := #0;
      'n': Result[I] := LF;
      'r': Result[I] := CR;
    end;
    Inc(I);
  until False;
end;

function CTCPQuote(const S: String): String;
begin
  Result := StringsReplace(S, [XDelim, XQuote], [XQuote+'a', XQuote+XQuote]);
end;

function CTCPUnquote(const S: String): String;
var
  I, L: Integer;
begin
  Result := S;
  L := Length(Result);
  I := 1;
  repeat
    I := PosIdx(XQuote, Result, I);
    if I = 0 then begin
      Break;
    end;
    IdDelete(Result, I, 1);
    Dec(L);
    if I > L then begin
      Break;
    end;
    if Result[I] = 'a' then begin
      Result[I] := XDelim;
    end;
    Inc(I);
  until False;
end;

procedure ExtractCTCPs(var AText: String; CTCPs: TStrings);
var
  LTmp: String;
  I, J, K: Integer;
begin
  I := 1;
  repeat
    J := PosIdx(XDelim, AText, I);
    if J = 0 then begin
      Break;
    end;
    K := PosIdx(XDelim, AText, J+1);
    if K = 0 then begin
      Break;
    end;
    LTmp := Copy(AText, J+1, K-J-1);
    LTmp := CTCPUnquote(LTmp);
    CTCPs.Add(LTmp);
    IdDelete(AText, J, (K-J)+1);
    I := J;
  until False;
end;

type
  TCICIdIRCCommandHandler = class(TIdCommandHandler)
  public
    procedure DoParseParams(AUnparsedParams: string; AParams: TStrings); override;
  end;

procedure TCICIdIRCCommandHandler.DoParseParams(AUnparsedParams: string; AParams: TStrings);
begin
  AParams.Clear;
  while AUnparsedParams <> '' do begin
    AParams.Add(FetchIRCParam(AUnparsedParams));
  end;
end;

function TCICIdIRC.GetCmdHandlerClass: TIdCommandHandlerClass;
begin
  Result := TCICIdIRCCommandHandler;
end;

procedure TCICIdIRC.InitComponent;
begin
  inherited InitComponent;
  //
  FReplies := TCICIdIRCReplies.Create;
  Port := IdPORT_IRC;
  FUserMode := [];

  // RLebeau 2/21/08: for the IRC protocol, RFC 2812 section 2.4 says that
  // clients are not allowed to issue numeric replies for server-issued
  // commands.  Added the PerformReplies property so TCICIdIRC can specify
  // that behavior.
  CommandHandlers.PerformReplies := False;

  // RLebeau 3/11/08: most of the command handlers should parse parameters by default
  CommandHandlers.ParseParamsDefault := True;

  if not IsDesignTime then begin
    AssignIRCClientCommands;
  end;
end;

destructor TCICIdIRC.Destroy;
begin
  FreeAndNil(FReplies);
  FreeAndNil(FBans);
  FreeAndNil(FExcepts);
  FreeAndNil(FInvites);
  FreeAndNil(FLinks);
  FreeAndNil(FMotd);
  FreeAndNil(FNames);
  FreeAndNil(FWho);
  FreeAndNil(FWhoIs);
  FreeAndNil(FWhoWas);
  FreeAndNil(FSvrList);
  FreeAndNil(FUsers);
  inherited Destroy;
end;

function TCICIdIRC.GetUserMode: String;
const
  IdIRCUserModeChars: array[TCICIdIRCUserMode] of Char = ('a', 'i', 'w', 'r', 'o', 'O', 's'); {do not localize}
var
  i: TCICIdIRCUserMode;
begin
  if FUserMode <> [] then
  begin
    Result := '+';
    for i := amAway to amReceiveServerNotices do begin
      if i in FUserMode then begin
        Result := Result + IdIRCUserModeChars[i];
      end;
    end;
  end else begin
    Result := '0';
  end;
end;

procedure TCICIdIRC.Connect;
begin
  // I doubt that there is explicit SSL support in the IRC protocol
  if (IOHandler is TIdSSLIOHandlerSocketBase) then begin
    (IOHandler as TIdSSLIOHandlerSocketBase).PassThrough := False;
  end;
  inherited Connect;
  //
  try
    FAltNickUsed := False;
    if FPassword <> '' then begin
      Raw(IndyFormat('PASS %s', [FPassword]));  {do not localize}
    end;
    SetNickname(FNickname);
    SetUsername(FUsername);
  except
    on E: EIdSocketError do begin
      inherited Disconnect;
      raise EIdIRCError.Create(RSIRCCannotConnect);
    end;
  end;
end;

procedure TCICIdIRC.Disconnect(const AReason: String = '');
begin
  Raw(IndyFormat('QUIT :%s', [AReason])); {do not localize}
  inherited Disconnect;
end;

procedure TCICIdIRC.Raw(const ALine: String);
begin
  if Connected then begin
    if Assigned(FOnRaw) then begin
      FOnRaw(nil, False, ALine);
    end;
    IOHandler.WriteLn(IRCQuote(ALine));
  end;
end;

procedure TCICIdIRC.AssignIRCClientCommands;
begin
  { Text commands }
  //PRIVMSG Nickname/#channel :message
  with CommandHandlers.Add do
  begin
    Command := 'PRIVMSG'; {do not localize}
    OnCommand := CommandPRIVMSG;
    ParseParams := False;
  end;
  //NOTICE Nickname/#channel :message
  with CommandHandlers.Add do
  begin
    Command := 'NOTICE';  {do not localize}
    OnCommand := CommandNOTICE;
    ParseParams := False;
  end;
  //JOIN #channel
  with CommandHandlers.Add do
  begin
    Command := 'JOIN';  {do not localize}
    OnCommand := CommandJOIN;
  end;
  //PART #channel
  with CommandHandlers.Add do
  begin
    Command := 'PART';  {do not localize}
    OnCommand := CommandPART;
  end;
  //KICK #channel target :reason
  with CommandHandlers.Add do
  begin
    Command := 'KICK';  {do not localize}
    OnCommand := CommandKICK;
  end;
  //MODE Nickname/#channel +/-modes parameters...
  with CommandHandlers.Add do
  begin
    Command := 'MODE';  {do not localize}
    OnCommand := CommandMODE;
    ParseParams := False;
  end;
  //NICK newNickname
  with CommandHandlers.Add do
  begin
    Command := 'NICK';  {do not localize}
    OnCommand := CommandNICK;
  end;
  //QUIT :reason
  with CommandHandlers.Add do
  begin
    Command := 'QUIT';  {do not localize}
    OnCommand := CommandQUIT;
  end;
  //SQUIT server :reason
  with CommandHandlers.Add do
  begin
    Command := 'SQUIT';  {do not localize}
    OnCommand := CommandSQUIT;
  end;
  //INVITE Nickname :#channel
  with CommandHandlers.Add do
  begin
    Command := 'INVITE';  {do not localize}
    OnCommand := CommandINVITE;
  end;
  //KILL Nickname :reason
  with CommandHandlers.Add do
  begin
    Command := 'KILL';  {do not localize}
    OnCommand := CommandKILL;
  end;
  //PING server
  with CommandHandlers.Add do
  begin
    Command := 'PING';  {do not localize}
    OnCommand := CommandPING;
  end;
  //WALLOPS :message
  with CommandHandlers.Add do
  begin
    Command := 'WALLOPS'; {do not localize}
    OnCommand := CommandWALLOPS;
    ParseParams := False;
  end;
  //TOPIC
  with CommandHandlers.Add do
  begin
    Command := 'TOPIC'; {do not localize}
    OnCommand := CommandTOPIC;
  end;
  //ERROR message
  with CommandHandlers.Add do
  begin
    Command := 'ERROR'; {do not localize}
    OnCommand := CommandERROR;
    ParseParams := False;
  end;

  { Numeric commands, refer to http://www.alien.net.au/irc/irc2numerics.html }
  //RPL_WELCOME
  with CommandHandlers.Add do
  begin
    Command := '001'; {do not localize}
    OnCommand := CommandWELCOME;
    ParseParams := False;
  end;
  //RPL_YOURHOST
  with CommandHandlers.Add do
  begin
    Command := '002'; {do not localize}
    OnCommand := CommandYOURHOST;
  end;
  //RPL_CREATED
  with CommandHandlers.Add do
  begin
    Command := '003'; {do not localize}
    OnCommand := CommandCREATED;
  end;
  //RPL_MYINFO
  with CommandHandlers.Add do
  begin
    Command := '004'; {do not localize}
    OnCommand := CommandMYINFO;
    ParseParams := False;
  end;
  //RPL_BOUNCE (deprecated), RPL_ISUPPORT (new)
  with CommandHandlers.Add do
  begin
    Command := '005'; {do not localize}
    //OnCommand := CommandBOUNCE; // deprecated
    OnCommand := CommandISUPPORT;
  end;
  { TODO:
  008  RPL_SNOMASK  ircu   Server notice mask (hex)
  009  RPL_STATMEMTOT  ircu
  014  RPL_YOURCOOKIE  Hybrid?
  042  RPL_YOURID  IRCnet
  043  RPL_SAVENICK  IRCnet  :<info>  Sent to the client when their nickname was forced to change due to a collision
  050  RPL_ATTEMPTINGJUNC  aircd
  051  RPL_ATTEMPTINGREROUTE  aircd
  200  RPL_TRACELINK  RFC1459  Link <version>[.<debug_level>] <destination> <next_server> [V<protocol_version> <link_uptime_in_seconds> <backstream_sendq> <upstream_sendq>]  See RFC
  201  RPL_TRACECONNECTING  RFC1459  Try. <class> <server>  See RFC
  202  RPL_TRACEHANDSHAKE  RFC1459  H.S. <class> <server>  See RFC
  203  RPL_TRACEUNKNOWN  RFC1459  ???? <class> [<connection_address>]  See RFC
  204  RPL_TRACEOPERATOR  RFC1459  Oper <class> <nick>  See RFC
  205  RPL_TRACEUSER  RFC1459  User <class> <nick>  See RFC
  206  RPL_TRACESERVER  RFC1459  Serv <class> <int>S <int>C <server> <nick!user|*!*>@<host|server> [V<protocol_version>]  See RFC
  207  RPL_TRACESERVICE  RFC2812  Service <class> <name> <type> <active_type>  See RFC
  208  RPL_TRACENEWTYPE  RFC1459  <newtype> 0 <client_name>  See RFC
  209  RPL_TRACECLASS  RFC2812  Class <class> <count>  See RFC
  210  RPL_TRACERECONNECT  RFC2812
  210  RPL_STATS  aircd   Used instead of having multiple stats numerics
  211  RPL_STATSLINKINFO  RFC1459  <linkname> <sendq> <sent_msgs> <sent_bytes> <recvd_msgs> <rcvd_bytes> <time_open>  Reply to STATS (See RFC)
  212  RPL_STATSCOMMANDS  RFC1459  <command> <count> [<byte_count> <remote_count>]  Reply to STATS (See RFC)
  213  RPL_STATSCLINE  RFC1459  C <host> * <name> <port> <class>  Reply to STATS (See RFC)
  214  RPL_STATSNLINE  RFC1459  N <host> * <name> <port> <class>  Reply to STATS (See RFC), Also known as RPL_STATSOLDNLINE (ircu, Unreal)
  215  RPL_STATSILINE  RFC1459  I <host> * <host> <port> <class>  Reply to STATS (See RFC)
  216  RPL_STATSKLINE  RFC1459  K <host> * <username> <port> <class>  Reply to STATS (See RFC)
  217  RPL_STATSQLINE  RFC1459
  217  RPL_STATSPLINE  ircu
  218  RPL_STATSYLINE
  }
  // RPL_BOUNCE (new)
  with CommandHandlers.Add do
  begin
    Command := '010'; {do not localize}
    OnCommand := CommandBOUNCE;
  end;
  //RPL_ENDOFSTATS
  with CommandHandlers.Add do
  begin
    Command := '219'; {do not localize}
    OnCommand := CommandENDOFSTATS;
  end;
  {TODO:
  221  RPL_UMODEIS  RFC1459  <user_modes> [<user_mode_params>]  Information about a user's own modes. Some daemons have extended the mode command and certain modes take parameters (like channel modes).
  }
  //RPL_SERVLIST
  with CommandHandlers.Add do
  begin
    Command := '234'; {do not localize}
    OnCommand := CommandSERVLIST;
  end;
  //RPL_SERVLISTEND
  with CommandHandlers.Add do
  begin
    Command := '235'; {do not localize}
    OnCommand := CommandSERVLISTEND;
  end;
  {TODO:
  236  RPL_STATSVERBOSE  ircu   Verbose server list?
  237  RPL_STATSENGINE  ircu   Engine name?
  239  RPL_STATSIAUTH  IRCnet
  241  RPL_STATSLLINE  RFC1459  L <hostmask> * <servername> <maxdepth>  Reply to STATS (See RFC)
  242  RPL_STATSUPTIME  RFC1459  :Server Up <days> days <hours>:<minutes>:<seconds>  Reply to STATS (See RFC)
  243  RPL_STATSOLINE  RFC1459  O <hostmask> * <nick> [:<info>]  Reply to STATS (See RFC); The info field is an extension found in some IRC daemons, which returns info such as an e-mail address or the name/job of an operator
  244  RPL_STATSHLINE  RFC1459  H <hostmask> * <servername>  Reply to STATS (See RFC)
  245  RPL_STATSSLINE  Bahamut, IRCnet, Hybrid
  250  RPL_STATSCONN  ircu, Unreal    
  251  RPL_LUSERCLIENT  RFC1459  :There are <int> users and <int> invisible on <int> servers  Reply to LUSERS command, other versions exist (eg. RFC2812); Text may vary.
  252  RPL_LUSEROP  RFC1459  <int> :<info>  Reply to LUSERS command - Number of IRC operators online
  253  RPL_LUSERUNKNOWN  RFC1459  <int> :<info>  Reply to LUSERS command - Number of unknown/unregistered connections
  254  RPL_LUSERCHANNELS  RFC1459  <int> :<info>  Reply to LUSERS command - Number of channels formed
  255  RPL_LUSERME  RFC1459  :I have <int> clients and <int> servers  Reply to LUSERS command - Information about local connections; Text may vary.
  256  RPL_ADMINME  RFC1459  <server> :<info>  Start of an RPL_ADMIN* reply. In practise, the server parameter is often never given, and instead the info field contains the text 'Administrative info about <server>'. Newer daemons seem to follow the RFC and output the server's hostname in the 'server' parameter, but also output the server name in the text as per traditional daemons.
  257  RPL_ADMINLOC1  RFC1459  :<admin_location>  Reply to ADMIN command (Location, first line)
  258  RPL_ADMINLOC2  RFC1459  :<admin_location>  Reply to ADMIN command (Location, second line)
  259  RPL_ADMINEMAIL  RFC1459  :<email_address>  Reply to ADMIN command (E-mail address of administrator)
  261  RPL_TRACELOG  RFC1459  File <logfile> <debug_level>  See RFC
  263  RPL_TRYAGAIN  RFC2812  <command> :<info>  When a server drops a command without processing it, it MUST use this reply. Also known as RPL_LOAD_THROTTLED and RPL_LOAD2HI, I'm presuming they do the same thing.
  265  RPL_LOCALUSERS  aircd, Hybrid, Hybrid, Bahamut   Also known as RPL_CURRENT_LOCAL
  266  RPL_GLOBALUSERS  aircd, Hybrid, Hybrid, Bahamut   Also known as RPL_CURRENT_GLOBAL
  267  RPL_START_NETSTAT  aircd
  268  RPL_NETSTAT  aircd
  269  RPL_END_NETSTAT  aircd
  270  RPL_PRIVS  ircu
  271  RPL_SILELIST  ircu
  272  RPL_ENDOFSILELIST  ircu
  273  RPL_NOTIFY  aircd
  276  RPL_VCHANEXIST     
  277  RPL_VCHANLIST
  278  RPL_VCHANHELP
  280  RPL_GLIST  ircu
  296  RPL_CHANINFO_KICKS  aircd
  299  RPL_END_CHANINFO  aircd
  300  RPL_NONE  RFC1459   Dummy reply, supposedly only used for debugging/testing new features, however has appeared in production daemons.
  }
  //RPL_AWAY
  with CommandHandlers.Add do
  begin
    Command := '301'; {do not localize}
    OnCommand := CommandAWAY;
  end;
  //RPL_USERHOST
  with CommandHandlers.Add do
  begin
    Command := '302'; {do not localize}
    OnCommand := CommandUSERHOST;
    ParseParams := False;
  end;
  //RPL_ISON
  with CommandHandlers.Add do
  begin
    Command := '303'; {do not localize}
    OnCommand := CommandISON;
  end;
  //RPL_UNAWAY
  with CommandHandlers.Add do
  begin
    Command := '305'; {do not localize}
    OnCommand := CommandAWAY;
  end;
  //RPL_NOWAWAY
  with CommandHandlers.Add do
  begin
    Command := '306'; {do not localize}
    OnCommand := CommandAWAY;
  end;
  //RPL_WHOISUSER
  with CommandHandlers.Add do
  begin
    Command := '311'; {do not localize}
    OnCommand := CommandWHOIS;
  end;
  //RPL_WHOISSERVER
  with CommandHandlers.Add do
  begin
    Command := '312'; {do not localize}
    OnCommand := CommandWHOIS;
  end;
  //RPL_WHOISOPERATOR
  with CommandHandlers.Add do
  begin
    Command := '313'; {do not localize}
    OnCommand := CommandWHOIS;
  end;
  //RPL_WHOWASUSER
  with CommandHandlers.Add do
  begin
    Command := '314';
    OnCommand := CommandWHOWAS;
  end;
  //RPL_ENDOFWHO
  with CommandHandlers.Add do
  begin
    Command := '315'; {do not localize}
    OnCommand := CommandENDOFWHO;
  end;
  //RPL_WHOISIDLE
  with CommandHandlers.Add do
  begin
    Command := '317'; {do not localize}
    OnCommand := CommandWHOIS;
  end;
  //RPL_ENDOFWHOIS
  with CommandHandlers.Add do
  begin
    Command := '318'; {do not localize}
    OnCommand := CommandENDOFWHOIS;
  end;
  //RPL_WHOISCHANNELS
  with CommandHandlers.Add do
  begin
    Command := '319'; {do not localize}
    OnCommand := CommandWHOIS;
  end;
  {TODO:
  320  RPL_WHOISVIRT  AustHex
  320  RPL_WHOIS_HIDDEN  Anothernet
  320  RPL_WHOISSPECIAL  Unreal
  }
  //RPL_LISTSTART
  with CommandHandlers.Add do
  begin
    Command := '321'; {do not localize}
    OnCommand := CommandLISTSTART;
  end;
  //RPL_LIST
  with CommandHandlers.Add do
  begin
    Command := '322'; {do not localize}
    OnCommand := CommandLIST;
  end;
  //RPL_LISTEND
  with CommandHandlers.Add do
  begin
    Command := '323'; {do not localize}
    OnCommand := CommandLISTEND;
  end;
  //RPL_CHANMODEIS
  with CommandHandlers.Add do
  begin
    Command := '324'; {do not localize}
    OnCommand := CommandCHANMODE;
  end;
  //RPL_UNIQOPIS
  with CommandHandlers.Add do
  begin
    Command := '325'; {do not localize}
    //OnCommand := CommandUNIQOP;
  end;
  {TODO:
  326  RPL_NOCHANPASS
  327  RPL_CHPASSUNKNOWN
  328  RPL_CHANNEL_URL  Bahamut, AustHex
  329  RPL_CREATIONTIME  Bahamut
  }
  //RPL_NOTOPIC
  with CommandHandlers.Add do
  begin
    Command := '331'; {do not localize}
    OnCommand := CommandTOPIC;
  end;
  //RPL_TOPIC
  with CommandHandlers.Add do
  begin
    Command := '332';
    OnCommand := CommandTOPIC;
  end;
  {TODO:
  333  RPL_TOPICWHOTIME  ircu
  339  RPL_BADCHANPASS
  340  RPL_USERIP  ircu
  }
  //RPL_INVITING
  with CommandHandlers.Add do
  begin
    Command := '341'; {do not localize}
    OnCommand := CommandINVITING;
  end;
  //RPL_SUMMONING
  with CommandHandlers.Add do
  begin
    Command := '342'; {do not localize}
    OnCommand := CommandSUMMONING;
  end;
  {TODO:
  345  RPL_INVITED  GameSurge  <channel> <user being invited> <user issuing invite> :<user being invited> has been invited by <user issuing invite>  Sent to users on a channel when an INVITE command has been issued
  }
  //RPL_INVITELIST
  with CommandHandlers.Add do
  begin
    Command := '346'; {do not localize}
    OnCommand := CommandINVITELIST;
  end;
  //RPL_ENDOFINVITELIST
  with CommandHandlers.Add do
  begin
    Command := '347'; {do not localize}
    OnCommand := CommandENDOFINVITELIST;
  end;
  //RPL_EXCEPTLIST
  with CommandHandlers.Add do
  begin
    Command := '348'; {do not localize}
    OnCommand := CommandEXCEPTLIST;
  end;
  //RPL_ENDOFEXCEPTLIST
  with CommandHandlers.Add do
  begin
    Command := '349'; {do not localize}
    OnCommand := CommandENDOFEXCEPTLIST;
  end;
  //RPL_VERSION
  with CommandHandlers.Add do
  begin
    Command := '351'; {do not localize}
    OnCommand := CommandVERSION;
  end;
  //RPL_WHOREPLY
  with CommandHandlers.Add do
  begin
    Command := '352'; {do not localize}
    OnCommand := CommandWHOREPLY;
  end;
  //RPL_NAMEREPLY
  with CommandHandlers.Add do
  begin
    Command := '353'; {do not localize}
    OnCommand := CommandNAMEREPLY;
  end;
  { TODO:
  354  RPL_WHOSPCRPL  ircu   Reply to WHO, however it is a 'special' reply because it is returned using a non-standard (non-RFC1459) format. The format is dictated by the command given by the user, and can vary widely. When this is used, the WHO command was invoked in its 'extended' form, as announced by the 'WHOX' ISUPPORT tag.
  355  RPL_NAMREPLY_  QuakeNet  ( '=' / '*' / '@' ) <channel> ' ' : [ '@' / '+' ] <nick> *( ' ' [ '@' / '+' ] <nick> )  Reply to the "NAMES -d" command - used to show invisible users (when the channel is set +D, QuakeNet relative). The proper define name for this numeric is unknown at this time Also see #353.
  }
  //RPL_LINKS
  with CommandHandlers.Add do
  begin
    Command := '364'; {do not localize}
    OnCommand := CommandLINKS;
  end;
  //RPL_ENDOFLINKS
  with CommandHandlers.Add do
  begin
    Command := '365'; {do not localize}
    OnCommand := CommandENDOFLINKS;
  end;
  //RPL_ENDOFNAMES
  with CommandHandlers.Add do
  begin
    Command := '366'; {do not localize}
    OnCommand := CommandENDOFNAMES;
  end;
  // RPL_BANLIST
  with CommandHandlers.Add do
  begin
    Command := '367'; {do not localize}
    OnCommand := CommandBANLIST;
  end;
  //RPL_ENDOFBANLIST
  with CommandHandlers.Add do
  begin
    Command := '368'; {do not localize}
    OnCommand := CommandENDOFBANLIST;
  end;
  //RPL_ENDOFWHOWAS
  with CommandHandlers.Add do
  begin
    Command := '369'; {do not localize}
    OnCommand := CommandENDOFWHOWAS;
  end;
  //RPL_INFO
  with CommandHandlers.Add do
  begin
    Command := '371'; {do not localize}
    OnCommand := CommandINFO;
  end;
  //RPL_MOTD
  with CommandHandlers.Add do
  begin
    Command := '372'; {do not localize}
    OnCommand := CommandMOTD;
  end;
  //RPL_ENDOFINFO
  with CommandHandlers.Add do
  begin
    Command := '374'; {do not localize}
    OnCommand := CommandENDOFINFO;
    ParseParams := False;
  end;
  //RPL_MOTDSTART
  with CommandHandlers.Add do
  begin
    Command := '375'; {do not localize}
    OnCommand := CommandMOTD;
  end;
  //RPL_ENDOFMOTD
  with CommandHandlers.Add do
  begin
    Command := '376'; {do not localize}
    OnCommand := CommandENDOFMOTD;
  end;
  //RPL_YOUREOPER
  with CommandHandlers.Add do
  begin
    Command := '381'; {do not localize}
    //OnCommand := CommandYOUAREOPER;
  end;
  //RPL_REHASHING
  with CommandHandlers.Add do
  begin
    Command := '382'; {do not localize}
    OnCommand := CommandREHASHING;
  end;
  //RPL_YOUARESERVICE
  with CommandHandlers.Add do
  begin
    Command := '383'; {do not localize}
    OnCommand := CommandSERVICE;
  end;
  {TODO:
  385  RPL_NOTOPERANYMORE  AustHex, Hybrid, Unreal
  388  RPL_ALIST  Unreal
  389  RPL_ENDOFALIST  Unreal
  }
  //RPL_TIME
  with CommandHandlers.Add do
  begin
    Command := '391'; {do not localize}
    OnCommand := CommandTIME;
  end;
  //RPL_USERSSTART
  with CommandHandlers.Add do
  begin
    Command := '392'; {do not localize}
    OnCommand := CommandUSERSSTART;
  end;
  //RPL_USERS
  with CommandHandlers.Add do
  begin
    Command := '393'; {do not localize}
    OnCommand := CommandUSERS;
  end;
  //RPL_ENDOFUSERS
  with CommandHandlers.Add do
  begin
    Command := '394'; {do not localize}
    OnCommand := CommandENDOFUSERS;
  end;
  //RPL_NOUSERS
  with CommandHandlers.Add do
  begin
    Command := '395'; {do not localize}
    OnCommand := CommandUSERS;
  end;
  //ERR_NICKNAMEINUSE
  with CommandHandlers.Add do
  begin
    // 433  ERR_NICKNAMEINUSE  RFC1459  <nick> :<reason>
    // Returned by the NICK command when the given nickname is already in use
    Command := '433'; {do not localize}
    OnCommand := CommandNICKINUSE;
  end;
  {TODO:
  396  RPL_HOSTHIDDEN  Undernet   Reply to a user when user mode +x (host masking) was set successfully
  400  ERR_UNKNOWNERROR   <command> [<?>] :<info>  Sent when an error occured executing a command, but it is not specifically known why the command could not be executed.
  401  ERR_NOSUCHNICK  RFC1459  <nick> :<reason>  Used to indicate the nickname parameter supplied to a command is currently unused
  402  ERR_NOSUCHSERVER  RFC1459  <server> :<reason>  Used to indicate the server name given currently doesn't exist
  403  ERR_NOSUCHCHANNEL  RFC1459  <channel> :<reason>  Used to indicate the given channel name is invalid, or does not exist
  404  ERR_CANNOTSENDTOCHAN  RFC1459  <channel> :<reason>  Sent to a user who does not have the rights to send a message to a channel
  405  ERR_TOOMANYCHANNELS  RFC1459  <channel> :<reason>  Sent to a user when they have joined the maximum number of allowed channels and they tried to join another channel
  406  ERR_WASNOSUCHNICK  RFC1459  <nick> :<reason>  Returned by WHOWAS to indicate there was no history information for a given nickname
  407  ERR_TOOMANYTARGETS  RFC1459  <target> :<reason>  The given target(s) for a command are ambiguous in that they relate to too many targets
  408  ERR_NOSUCHSERVICE  RFC2812  <service_name> :<reason>  Returned to a client which is attempting to send an SQUERY (or other message) to a service which does not exist
  409  ERR_NOORIGIN  RFC1459  :<reason>  PING or PONG message missing the originator parameter which is required since these commands must work without valid prefixes
  411  ERR_NORECIPIENT  RFC1459  :<reason>  Returned when no recipient is given with a command
  412  ERR_NOTEXTTOSEND  RFC1459  :<reason>  Returned when NOTICE/PRIVMSG is used with no message given
  413  ERR_NOTOPLEVEL  RFC1459  <mask> :<reason>  Used when a message is being sent to a mask without being limited to a top-level domain (i.e. * instead of *.au)
  414  ERR_WILDTOPLEVEL  RFC1459  <mask> :<reason>  Used when a message is being sent to a mask with a wild-card for a top level domain (i.e. *.*)
  415  ERR_BADMASK  RFC2812  <mask> :<reason>  Used when a message is being sent to a mask with an invalid syntax
  416  ERR_TOOMANYMATCHES  IRCnet  <command> [<mask>] :<info>  Returned when too many matches have been found for a command and the output has been truncated. An example would be the WHO command, where by the mask '*' would match everyone on the network! Ouch!
  416  ERR_QUERYTOOLONG  ircu   Same as ERR_TOOMANYMATCHES
  419  ERR_LENGTHTRUNCATED  aircd
  421  ERR_UNKNOWNCOMMAND  RFC1459  <command> :<reason>  Returned when the given command is unknown to the server (or hidden because of lack of access rights)
  422  ERR_NOMOTD  RFC1459  :<reason>  Sent when there is no MOTD to send the client
  423  ERR_NOADMININFO  RFC1459  <server> :<reason>  Returned by a server in response to an ADMIN request when no information is available. RFC1459 mentions this in the list of numerics. While it's not listed as a valid reply in section 4.3.7 ('Admin command'), it's confirmed to exist in the real world.
  424  ERR_FILEERROR  RFC1459  :<reason>  Generic error message used to report a failed file operation during the processing of a command
  425  ERR_NOOPERMOTD  Unreal
  429  ERR_TOOMANYAWAY  Bahamut
  430  ERR_EVENTNICKCHANGE  AustHex   Returned by NICK when the user is not allowed to change their nickname due to a channel event (channel mode +E)
  431  ERR_NONICKNAMEGIVEN  RFC1459  :<reason>  Returned when a nickname parameter expected for a command isn't found
  432  ERR_ERRONEUSNICKNAME  RFC1459  <nick> :<reason>  Returned after receiving a NICK message which contains a nickname which is considered invalid, such as it's reserved ('anonymous') or contains characters considered invalid for nicknames. This numeric is misspelt, but remains with this name for historical reasons :)
  436  ERR_NICKCOLLISION  RFC1459  <nick> :<reason>  Returned by a server to a client when it detects a nickname collision
  439  ERR_TARGETTOOFAST  ircu   Also known as many other things, RPL_INVTOOFAST, RPL_MSGTOOFAST etc  
  440  ERR_SERVICESDOWN  Bahamut, Unreal
  441  ERR_USERNOTINCHANNEL  RFC1459  <nick> <channel> :<reason>  Returned by the server to indicate that the target user of the command is not on the given channel
  442  ERR_NOTONCHANNEL  RFC1459  <channel> :<reason>  Returned by the server whenever a client tries to perform a channel effecting command for which the client is not a member  
  443  ERR_USERONCHANNEL  RFC1459  <nick> <channel> [:<reason>]  Returned when a client tries to invite a user to a channel they're already on  
  444  ERR_NOLOGIN  RFC1459  <user> :<reason>  Returned by the SUMMON command if a given user was not logged in and could not be summoned  
  445  ERR_SUMMONDISABLED  RFC1459  :<reason>  Returned by SUMMON when it has been disabled or not implemented  
  446  ERR_USERSDISABLED  RFC1459  :<reason>  Returned by USERS when it has been disabled or not implemented  
  447  ERR_NONICKCHANGE  Unreal    
  449  ERR_NOTIMPLEMENTED  Undernet  Unspecified  Returned when a requested feature is not implemented (and cannot be completed)  
  451  ERR_NOTREGISTERED  RFC1459  :<reason>  Returned by the server to indicate that the client must be registered before the server will allow it to be parsed in detail  
  452  ERR_IDCOLLISION     
  453  ERR_NICKLOST     
  455  ERR_HOSTILENAME  Unreal    
  456  ERR_ACCEPTFULL     
  457  ERR_ACCEPTEXIST
  458  ERR_ACCEPTNOT     
  459  ERR_NOHIDING  Unreal   Not allowed to become an invisible operator?  
  460  ERR_NOTFORHALFOPS  Unreal    
  461  ERR_NEEDMOREPARAMS  RFC1459  <command> :<reason>  Returned by the server by any command which requires more parameters than the number of parameters given  
  462  ERR_ALREADYREGISTERED  RFC1459  :<reason>  Returned by the server to any link which attempts to register again  
  463  ERR_NOPERMFORHOST  RFC1459  :<reason>  Returned to a client which attempts to register with a server which has been configured to refuse connections from the client's host  
  464  ERR_PASSWDMISMATCH  RFC1459  :<reason>  Returned by the PASS command to indicate the given password was required and was either not given or was incorrect  
  465  ERR_YOUREBANNEDCREEP  RFC1459  :<reason>  Returned to a client after an attempt to register on a server configured to ban connections from that client  
  466  ERR_YOUWILLBEBANNED  RFC1459   Sent by a server to a user to inform that access to the server will soon be denied  
  467  ERR_KEYSET  RFC1459  <channel> :<reason>  Returned when the channel key for a channel has already been set  
  468  ERR_INVALIDUSERNAME  ircu
  468  ERR_ONLYSERVERSCANCHANGE  Bahamut, Unreal    
  469  ERR_LINKSET  Unreal    
  470  ERR_LINKCHANNEL  Unreal    
  470  ERR_KICKEDFROMCHAN  aircd    
  471  ERR_CHANNELISFULL  RFC1459  <channel> :<reason>  Returned when attempting to join a channel which is set +l and is already full  
  472  ERR_UNKNOWNMODE  RFC1459  <char> :<reason>  Returned when a given mode is unknown  
  473  ERR_INVITEONLYCHAN  RFC1459  <channel> :<reason>  Returned when attempting to join a channel which is invite only without an invitation  
  474  ERR_BANNEDFROMCHAN  RFC1459  <channel> :<reason>  Returned when attempting to join a channel a user is banned from  
  475  ERR_BADCHANNELKEY  RFC1459  <channel> :<reason>  Returned when attempting to join a key-locked channel either without a key or with the wrong key  
  476  ERR_BADCHANMASK  RFC2812  <channel> :<reason>  The given channel mask was invalid
  478  ERR_BANLISTFULL  RFC2812  <channel> <char> :<reason>  Returned when a channel access list (i.e. ban list etc) is full and cannot be added to
  479  ERR_BADCHANNAME  Hybrid
  479  ERR_LINKFAIL  Unreal
  481  ERR_NOPRIVILEGES  RFC1459  :<reason>  Returned by any command requiring special privileges (eg. IRC operator) to indicate the operation was unsuccessful
  482  ERR_CHANOPRIVSNEEDED  RFC1459  <channel> :<reason>  Returned by any command requiring special channel privileges (eg. channel operator) to indicate the operation was unsuccessful
  483  ERR_CANTKILLSERVER  RFC1459  :<reason>  Returned by KILL to anyone who tries to kill a server
  485  ERR_UNIQOPRIVSNEEDED  RFC2812  :<reason>  Any mode requiring 'channel creator' privileges returns this error if the client is attempting to use it while not a channel creator on the given channel
  488  ERR_TSLESSCHAN  IRCnet
  491  ERR_NOOPERHOST  RFC1459  :<reason>  Returned by OPER to a client who cannot become an IRC operator because the server has been configured to disallow the client's host
  493  ERR_NOFEATURE  ircu    
  494  ERR_BADFEATURE  ircu
  495  ERR_BADLOGTYPE  ircu
  496  ERR_BADLOGSYS  ircu
  497  ERR_BADLOGVALUE  ircu
  498  ERR_ISOPERLCHAN  ircu
  499  ERR_CHANOWNPRIVNEEDED  Unreal   Works just like ERR_CHANOPRIVSNEEDED except it indicates that owner status (+q) is needed. Also see #482.
  501  ERR_UMODEUNKNOWNFLAG  RFC1459  :<reason>  Returned by the server to indicate that a MODE message was sent with a nickname parameter and that the mode flag sent was not recognised
  502  ERR_USERSDONTMATCH  RFC1459  :<reason>  Error sent to any user trying to view or change the user mode for a user other than themselves
  503  ERR_GHOSTEDCLIENT  Hybrid
  504  ERR_USERNOTONSERV
  511  ERR_SILELISTFULL  ircu
  512  ERR_TOOMANYWATCH  Bahamut   Also known as ERR_NOTIFYFULL (aircd), I presume they are the same
  513  ERR_BADPING  ircu   Also known as ERR_NEEDPONG (Unreal/Ultimate) for use during registration, however it's not used in Unreal (and might not be used in Ultimate either).
  515  ERR_BADEXPIRE  ircu    
  516  ERR_DONTCHEAT  ircu
  517  ERR_DISABLED  ircu  <command> :<info/reason>
  522  ERR_WHOSYNTAX  Bahamut
  523  ERR_WHOLIMEXCEED  Bahamut
  525  ERR_REMOTEPFX  CAPAB USERCMDPFX  <nickname> :<reason>  Proposed.
  526  ERR_PFXUNROUTABLE  CAPAB USERCMDPFX  <nickname> :<reason>  Proposed.
  550  ERR_BADHOSTMASK  QuakeNet
  551  ERR_HOSTUNAVAIL  QuakeNet
  552  ERR_USINGSLINE  QuakeNet
  600  RPL_LOGON  Bahamut, Unreal
  601  RPL_LOGOFF  Bahamut, Unreal
  602  RPL_WATCHOFF  Bahamut, Unreal
  603  RPL_WATCHSTAT  Bahamut, Unreal
  604  RPL_NOWON  Bahamut, Unreal
  605  RPL_NOWOFF  Bahamut, Unreal
  606  RPL_WATCHLIST  Bahamut, Unreal
  607  RPL_ENDOFWATCHLIST  Bahamut, Unreal
  608  RPL_WATCHCLEAR  Ultimate
  611  RPL_ISLOCOP  Ultimate
  612  RPL_ISNOTOPER  Ultimate
  613  RPL_ENDOFISOPER  Ultimate
  618  RPL_DCCLIST
  624  RPL_OMOTDSTART  Ultimate    
  625  RPL_OMOTD  Ultimate
  626  RPL_ENDOFO Ultimate
  630  RPL_SETTINGS  Ultimate
  631  RPL_ENDOFSETTINGS  Ultimate
  660  RPL_TRACEROUTE_HOP  KineIRCd  <target> <hop#> [<address> [<hostname> | '*'] <usec_ping>]  Returned from the TRACEROUTE IRC-Op command when tracerouting a host
  661  RPL_TRACEROUTE_START  KineIRCd  <target> <target_FQDN> <target_address> <max_hops>  Start of an RPL_TRACEROUTE_HOP list
  662  RPL_MODECHANGEWARN  KineIRCd  ['+' | '-']<mode_char> :<warning>  Plain text warning to the user about turning on or off a user mode. If no '+' or '-' prefix is used for the mode char, '+' is presumed.
  663  RPL_CHANREDIR  KineIRCd  <old_chan> <new_chan> :<info>  Used to notify the client upon JOIN that they are joining a different channel than expected because the IRC Daemon has been set up to map the channel they attempted to join to the channel they eventually will join.
  664  RPL_SERVMODEIS  KineIRCd  <server> <modes> <parameters>..  Reply to MODE <servername>. KineIRCd supports server modes to simplify configuration of servers; Similar to RPL_CHANNELMODEIS
  665  RPL_OTHERUMODEIS  KineIRCd  <nickname> <modes>  Reply to MODE <nickname> to return the user-modes of another user to help troubleshoot connections, etc. Similar to RPL_UMODEIS, however including the target
  666  RPL_ENDOF_GENERIC  KineIRCd  <command> [<parameter> ...] :<info>  Generic response for new lists to save numerics.
  670  RPL_WHOWASDETAILS  KineIRCd  <nick> <type> :<information>  Returned by WHOWAS to return extended information (if available). The type field is a number indication what kind of information.
  671  RPL_WHOISSECURE  KineIRCd  <nick> <type> [:<info>]  Reply to WHOIS command - Returned if the target is connected securely, eg. type may be TLSv1, or SSLv2 etc. If the type is unknown, a '*' may be used.
  672  RPL_UNKNOWNMODES  Ithildin  <modes> :<info>  Returns a full list of modes that are unknown when a client issues a MODE command (rather than one numeric per mode)
  673  RPL_CANNOTSETMODES  Ithildin  <modes> :<info>  Returns a full list of modes that cannot be set when a client issues a MODE command
  678  RPL_LUSERSTAFF  KineIRCd  <staff_online_count> :<info>  Reply to LUSERS command - Number of network staff (or 'helpers') online (differs from Local/Global operators). Similar format to RPL_LUSEROP
  679  RPL_TIMEONSERVERIS  KineIRCd  <seconds> [<nanoseconds> | '0'] <timezone> <flags> :<info>  Optionally sent upon connection, and/or sent as a reply to the TIME command. This returns the time on the server in a uniform manner. The seconds (and optionally nanoseconds) is the time since the UNIX Epoch, and is used since many existing timestamps in the IRC-2 protocol are done this way (i.e. ban lists). The timezone is hours and minutes each of Greenwich ('[+/-]HHMM'). Since all timestamps sent from the server are in a similar format, this numeric is designed to give clients the ability to provide accurate timestamps to their users.
  682  RPL_NETWORKS  KineIRCd  <name> <through_name> <hops> :<info>  A reply to the NETWORKS command when requesting a list of known networks (within the IIRC domain).
  687  RPL_YOURLANGUAGEIS  KineIRCd  <code(s)> :<info>  Reply to the LANGUAGE command, informing the client of the language(s) it has set
  688  RPL_LANGUAGE  KineIRCd  <code> <revision> <maintainer> <flags> * :<info>  A language reply to LANGUAGE when requesting a list of known languages
  689  RPL_WHOISSTAFF  KineIRCd  :<info>  The user is a staff member. The information may explain the user's job role, or simply state that they are a part of the network staff. Staff members are not IRC operators, but rather people who have special access in association with network services. KineIRCd uses this numeric instead of the existing numerics due to the overwhelming number of conflicts.
  690  RPL_WHOISLANGUAGE  KineIRCd  <nick> <language codes>  Reply to WHOIS command - A list of languages someone can speak. The language codes are comma delimitered.
  702  RPL_MODLIST  RatBox  <?> 0x<?> <?> <?>  Output from the MODLIST command
  703  RPL_ENDOFMODLIST  RatBox  :<text>  Terminates MODLIST output
  704  RPL_HELPSTART  RatBox  <command> :<text>  Start of HELP command output
  705  RPL_HELPTXT  RatBox  <command> :<text>  Output from HELP command
  706  RPL_ENDOFHELP  RatBox  <command> :<text>  End of HELP command output
  708  RPL_ETRACEFULL  RatBox  <?> <?> <?> <?> <?> <?> <?> :<?>  Output from 'extended' trace  
  709  RPL_ETRACE  RatBox  <?> <?> <?> <?> <?> <?> :<?>  Output from 'extended' trace  
  710  RPL_KNOCK  RatBox  <channel> <nick>!<user>@<host> :<text>  Message delivered using KNOCK command  
  711  RPL_KNOCKDLVR  RatBox  <channel> :<text>  Message returned from using KNOCK command  
  712  ERR_TOOMANYKNOCK  RatBox  <channel> :<text>  Message returned when too many KNOCKs for a channel have been sent by a user  
  713  ERR_CHANOPEN  RatBox  <channel> :<text>  Message returned from KNOCK when the channel can be freely joined by the user  
  714  ERR_KNOCKONCHAN  RatBox  <channel> :<text>  Message returned from KNOCK when the user has used KNOCK on a channel they have already joined  
  715  ERR_KNOCKDISABLED  RatBox  :<text>  Returned from KNOCK when the command has been disabled  
  716  RPL_TARGUMODEG  RatBox  <nick> :<info>  Sent to indicate the given target is set +g (server-side ignore)  
  717  RPL_TARGNOTIFY  RatBox  <nick> :<info>  Sent following a PRIVMSG/NOTICE to indicate the target has been notified of an attempt to talk to them while they are set +g  
  718  RPL_UMODEGMSG  RatBox  <nick> <user>@<host> :<info>  Sent to a user who is +g to inform them that someone has attempted to talk to them (via PRIVMSG/NOTICE), and that they will need to be accepted (via the ACCEPT command) before being able to talk to them  
  720  RPL_OMOTDSTART  RatBox  :<text>  IRC Operator MOTD header, sent upon OPER command  
  721  RPL_OMOTD  RatBox  :<text>  IRC Operator MOTD text (repeated, usually)  
  722  RPL_ENDOFOMOTD  RatBox  :<text>  IRC operator MOTD footer  
  723  ERR_NOPRIVS  RatBox  <command> :<text>  Returned from an oper command when the IRC operator does not have the relevant operator privileges.  
  724  RPL_TESTMARK  RatBox  <nick>!<user>@<host> <?> <?> :<text>  Reply from an oper command reporting how many users match a given user@host mask  
  725  RPL_TESTLINE  RatBox  <?> <?> <?> :<?>  Reply from an oper command reporting relevant I/K lines that will match a given user@host  
  726  RPL_NOTESTLINE  RatBox  <?> :<text>  Reply from oper command reporting no I/K lines match the given user@host  
  771  RPL_XINFO  Ithildin   Used to send 'eXtended info' to the client, a replacement for the STATS command to send a large variety of data and minimise numeric pollution.  
  773  RPL_XINFOSTART  Ithildin   Start of an RPL_XINFO list  
  774  RPL_XINFOEND  Ithildin   Termination of an RPL_XINFO list  
  972  ERR_CANNOTDOCOMMAND  Unreal   Works similarly to all of KineIRCd's CANNOT* numerics. This one indicates that a command could not be performed for an arbitrary reason. For example, a halfop trying to kick an op.  
  973  ERR_CANNOTCHANGEUMODE  KineIRCd  <mode_char> :<reason>  Reply to MODE when a user cannot change a user mode  
  974  ERR_CANNOTCHANGECHANMODE  KineIRCd  <mode_char> :<reason>  Reply to MODE when a user cannot change a channel mode  
  975  ERR_CANNOTCHANGESERVERMODE  KineIRCd  <mode_char> :<reason>  Reply to MODE when a user cannot change a server mode
  976  ERR_CANNOTSENDTONICK  KineIRCd  <nick> :<reason>  Returned from NOTICE, PRIVMSG or other commands to notify the user that they cannot send a message to a particular client. Similar to ERR_CANNOTSENDTOCHAN. KineIRCd uses this in conjunction with user-mode +R to allow users to block people who are not identified to services (spam avoidance)  
  977  ERR_UNKNOWNSERVERMODE  KineIRCd  <modechar> :<info>  Returned by MODE to inform the client they used an unknown server mode character.  
  979  ERR_SERVERMODELOCK  KineIRCd  <target> :<info>  Returned by MODE to inform the client the server has been set mode +L by an administrator to stop server modes being changed  
  980  ERR_BADCHARENCODING  KineIRCd  <command> <charset> :<info>  Returned by any command which may have had the given data modified because one or more glyphs were incorrectly encoded in the current charset (given). Such a use would be where an invalid UTF-8 sequence was given which may be considered insecure, or defines a character which is invalid within that context. For safety reasons, the invalid character is not returned to the client.  
  981  ERR_TOOMANYLANGUAGES  KineIRCd  <max_langs> :<info>  Returned by the LANGUAGE command to tell the client they cannot set as many languages as they have requested. To assist the client, the maximum languages which can be set at one time is given, and the language settings are not changed.  
  982  ERR_NOLANGUAGE  KineIRCd  <language_code> :<info>  Returned by the LANGUAGE command to tell the client it has specified an unknown language code.  
  983  ERR_TEXTTOOSHORT  KineIRCd  <command> :<info>  Returned by any command requiring text (such as a message or a reason), which was not long enough to be considered valid. This was created initially to combat '/wallops foo' abuse, but is also used by DIE and RESTART commands to attempt to encourage meaningful reasons.  
  999  ERR_NUMERIC_ERR  Bahamut  
  }

  FCommandHandlers.OnBeforeCommandHandler := DoBeforeCmd;
end;

{ Command handlers }

procedure TCICIdIRC.DoBeforeCmd(ASender: TIdCommandHandlers; var AData: string; AContext: TIdContext);
var
  LTmp: String;
begin
  AData := IRCUnquote(AData);
  // ":nickname!user@host"
  if TextStartsWith(AData, ':') then begin
    LTmp := Fetch(AData, ' ');
    Delete(LTmp, 1, 1); // remove ':'
    FSenderNick := Fetch(LTmp, '!');
    FSenderHost := LTmp;
  end else begin
    FSenderNick := '';
    FSenderHost := '';
  end;
  if Assigned(FOnRaw) then begin
    FOnRaw(AContext, True, AData);
  end;
end;

procedure TCICIdIRC.DoReplyUnknownCommand(AContext: TIdContext; ALine: string);
var
  ACmdCode: Integer;
begin
  ACmdCode := IndyStrToInt(Fetch(ALine, ' '), -1);
  //
  case ACmdCode of
    6,
    7:
      begin
        //MAP
      end;
    5,
    400..424,
    437..502:
      begin
        if Assigned(FOnServerError) then begin
          OnServerError(AContext, ACmdCode, ALine);
        end;
      end;
    431..432,
    436:
      begin
        if Assigned(FOnNickError) then begin
          OnNicknameError(AContext, ACmdCode);
        end;
      end;
  end;
end;

procedure TCICIdIRC.CommandPRIVMSG(ASender: TIdCommand);
var
  LTmp, LTarget, LData, LCTCP: String;
  I: Integer;
  CTCPList: TStringList;
begin
  LTmp := ASender.UnparsedParams;
  LTarget := FetchIRCParam(LTmp);
  LData := FetchIRCParam(LTmp);

  CTCPList := TStringList.Create;
  try
    ExtractCTCPs(LData, CTCPList);
    if CTCPList.Count = 0 then begin
      if Assigned(FOnPrivMessage) then begin
        OnPrivateMessage(ASender.Context, FSenderNick, FSenderHost, LTarget, LData);
      end;
    end else
    begin
      if (LData <> '') and Assigned(FOnPrivMessage) then begin
        OnPrivateMessage(ASender.Context, FSenderNick, FSenderHost, LTarget, LData);
      end;
      for I := 0 to CTCPList.Count - 1 do begin
        LData := CTCPList[I];
        LCTCP := Fetch(LData, ' ');
        case PosInStrArray(LCTCP, IdIRCCTCP) of
          0: { ACTION }
            begin
              {
              if Assigned(FOnAction) then begin
                FOnAction(ASender.Context, FSenderNick, FSenderHost, LTarget, LData);
              end;
              }
              if Assigned(FOnCTCPQry) then begin
                if (NOT FOnCTCPQry(ASender.Context, FSenderNick, FSenderHost, LTarget, LCTCP, LData)) then
                   CTCPReply(FSenderNick, 'ERRMSG', LCTCP +' ' + LData + ' unknown query'); {do not localize}
              end
              else
                CTCPReply(FSenderNick, 'ERRMSG', LCTCP +' ' + LData + ' unknown query'); {do not localize}
            end;
          1: { SOUND }
            begin
              {
              if Assigned(FOnSound) then begin
                FOnSound(ASender.Context, FSenderNick, FSenderHost, LTarget, LData);
              end;
              }
              if Assigned(FOnCTCPQry) then begin
                FOnCTCPQry(ASender.Context, FSenderNick, FSenderHost, LTarget, LCTCP, LData);
              end;
              CTCPReply(FSenderNick, 'ERRMSG', LCTCP +' ' + LData + ' unknown query'); {do not localize}
            end;
          2: { PING }
            begin
              {
              LTmp := '';
              if Assigned(FOnPing) then begin
                FOnPing(ASender.Context, LTmp);
              end;
              if LTmp = '' then begin
                LTmp := DateTimeToStr(Now);
              end;
              CTCPReply(FSenderNick, LCTCP, ':' + LTmp);
              }
              if Assigned(FOnCTCPQry) then begin
                FOnCTCPQry(ASender.Context, FSenderNick, FSenderHost, LTarget, LCTCP, LData);
              end;
              // AWinkelsdorf 3/10/2010 ToDo: CTCP Ping might need a CTIME result but
              // many clients do not send the required CTIME with the Ping Query...
              CTCPReply(FSenderNick, LCTCP, DateTimeToStr(Now)); {do not localize}
            end;
          3: { FINGER }
            begin
              CTCPReply(FSenderNick, LCTCP, Replies.Finger); {do not localize}
            end;
          4: { USERINFO }
            begin
              CTCPReply(FSenderNick, LCTCP, Replies.UserInfo); {do not localize}
            end;
          5: { VERSION }
            begin
              CTCPReply(FSenderNick, LCTCP, Replies.Version); {do not localize}
            end;
          6: { CLIENTINFO }
            begin
              // TODO: add OnClientInfoQuery event to handle per-command queries
              CTCPReply(FSenderNick, LCTCP, Replies.ClientInfo); {do not localize}
            end;
          7: { TIME }
            begin
              CTCPReply(FSenderNick, LCTCP, DateTimeToStr(Now));
            end;
          8: { ERROR }
            begin
              CTCPReply(FSenderNick, LCTCP, LData + ' No Error'); {do not localize}
            end;
          9: { DCC }
            begin
              ParseDCC(ASender.Context, LData);
            end;
          10: { SED }
            begin
              //ParseSED(AContext, LData);
              if Assigned(FOnCTCPQry) then begin
                FOnCTCPQry(ASender.Context, FSenderNick, FSenderHost, LTarget, LCTCP, LData);
              end;
              CTCPReply(FSenderNick, LCTCP, LData + ' unknown query'); {do not localize}
            end;
          else
            begin
              if Assigned(FOnCTCPQry) then begin
                FOnCTCPQry(ASender.Context, FSenderNick, FSenderHost, LTarget, LCTCP, LData);
              end;
              CTCPReply(FSenderNick, LCTCP, LData + ' unknown query'); {do not localize}
            end;
        end;
      end;
    end;
  finally
    CTCPList.Free;
  end;
end;

procedure TCICIdIRC.CommandNOTICE(ASender: TIdCommand);
var
  LTmp, LTarget, LData, LCTCP: String;
  I: Integer;
  CTCPList: TStringList;
begin
  LTmp := ASender.UnparsedParams;
  LTarget := FetchIRCParam(LTmp);
  LData := FetchIRCParam(LTmp);

  CTCPList := TStringList.Create;
  try
    ExtractCTCPs(LData, CTCPList);
    if CTCPList.Count = 0 then begin
      if Assigned(FOnNotice) then begin
        OnNotice(ASender.Context, FSenderNick, FSenderHost, LTarget, LData);
      end;
    end else
    begin
      if (LData <> '') and Assigned(FOnNotice) then begin
        OnNotice(ASender.Context, FSenderNick, FSenderHost, LTarget, LData);
      end;
      for I := 0 to CTCPList.Count - 1 do begin
        LData := CTCPList[I];
        LCTCP := Fetch(LData, ' ');
        case PosInStrArray(LCTCP, IdIRCCTCP) of
          0: { ACTION }
            begin
              {
              if Assigned(FOnAction) then begin
                FOnAction(ASender.Context, FSenderNick, FSenderHost, LTarget, LData);
              end;
              }
              if Assigned(FOnCTCPRep) then begin
                FOnCTCPRep(ASender.Context, FSenderNick, FSenderHost, LTarget, LCTCP, LData);
              end;
            end;
          9: { DCC }
            begin
              ParseDCC(ASender.Context, LData);
            end;
          10: { SED }
            begin
              //ParseSED(AContext, LData);
              if Assigned(FOnCTCPRep) then begin
                FOnCTCPRep(ASender.Context, FSenderNick, FSenderHost, LTarget, LCTCP, LData);
              end;
            end;
        else
          if Assigned(FOnCTCPRep) then begin
            FOnCTCPRep(ASender.Context, FSenderNick, FSenderHost, LTarget, LCTCP, LData);
          end;
        end;
      end;
    end;
  finally
    CTCPList.Free;
  end;
end;

procedure TCICIdIRC.CommandJOIN(ASender: TIdCommand);
begin
  if Assigned(FOnJoin) then begin
    OnJoin(ASender.Context, FSenderNick, FSenderHost, ASender.Params[0]);
  end;
end;

procedure TCICIdIRC.CommandPART(ASender: TIdCommand);
var
  LChannel, LMsg: string;
begin
  if Assigned(FOnPart) then begin
    if ASender.Params.Count > 0 then begin
      LChannel := ASender.Params[0];
    end;
    if ASender.Params.Count > 1 then begin
      LMsg := ASender.Params[1];
    end;
    OnPart(ASender.Context, FSenderNick, FSenderHost, LChannel, LMsg);
  end;
end;

procedure TCICIdIRC.CommandKICK(ASender: TIdCommand);
var
  LChannel, LTarget, LReason: string;
begin
  if Assigned(FOnKick) then begin
    if ASender.Params.Count > 0 then begin
      LChannel := ASender.Params[0];
    end;
    if ASender.Params.Count > 1 then begin
      LTarget := ASender.Params[1];
    end;
    if ASender.Params.Count > 2 then begin
      LReason := ASender.Params[2];
    end;
    OnKick(ASender.Context, FSenderNick, FSenderHost, LChannel, LTarget, LReason);
  end;
end;

procedure TCICIdIRC.CommandMODE(ASender: TIdCommand);
var
  LTmp, LParam: String;
begin
  LTmp := ASender.UnparsedParams;
  LParam := FetchIRCParam(LTmp);
  if IsChannel(LParam) then begin
    if Assigned(FOnChanMode) then begin
      OnChannelMode(ASender.Context, FSenderNick, FSenderHost, LParam, LTmp, '');
    end;
  end
  else if Assigned(FOnUserMode) then begin
    OnUserMode(ASender.Context, FSenderNick, FSenderHost, LTmp);
  end;
end;

procedure TCICIdIRC.CommandNICK(ASender: TIdCommand);
begin
  if Assigned(FOnNickChange) then begin
    OnNicknameChange(ASender.Context, FSenderNick, FSenderHost, ASender.Params[0]);
  end;
end;

procedure TCICIdIRC.CommandQUIT(ASender: TIdCommand);
var
  LReason: string;
begin
  if Assigned(FOnQuit) then begin
    if ASender.Params.Count > 0 then begin
      LReason := ASender.Params[0];
    end;
    OnQuit(ASender.Context, FSenderNick, FSenderHost, LReason);
  end;
end;

procedure TCICIdIRC.CommandSQUIT(ASender: TIdCommand);
var
  LServer, LComment: string;
begin
  if Assigned(FOnSvrQuit) then begin
    if ASender.Params.Count > 0 then begin
      LServer := ASender.Params[0];
    end;
    if ASender.Params.Count > 1 then begin
      LComment := ASender.Params[1];
    end;
    OnServerQuit(ASender.Context, FSenderNick, FSenderHost, LServer, LComment);
  end;
end;

procedure TCICIdIRC.CommandINVITE(ASender: TIdCommand);
begin
  if Assigned(FOnInvite) then begin
    OnInvite(ASender.Context, FSenderNick, FSenderHost, ASender.Params[0], ASender.Params[1]);
  end;
end;

procedure TCICIdIRC.CommandKILL(ASender: TIdCommand);
var
  LTarget, LReason: string;
begin
  if Assigned(FOnKill) then begin
    if ASender.Params.Count > 0 then begin
      LTarget := ASender.Params[0];
    end;
    if ASender.Params.Count > 1 then begin
      LReason := ASender.Params[1];
    end;
    OnKill(ASender.Context, FSenderNick, FSenderHost, LTarget, LReason);
  end;
end;

procedure TCICIdIRC.CommandPING(ASender: TIdCommand);
var
  LServer1: String;
begin
  if ASender.Params.Count > 0 then begin
    LServer1 := ASender.Params[0];
  end;
  Pong(LServer1);
  if Assigned(FOnPingPong) then begin
    OnPingPong(ASender.Context);
  end;
end;

procedure TCICIdIRC.CommandWALLOPS(ASender: TIdCommand);
var
  LTmp: string;
begin
  if Assigned(FOnWallops) then begin
    LTmp := ASender.UnparsedParams;
    OnWallops(ASender.Context, FSenderNick, FSenderHost, FetchIRCParam(LTmp));
  end;
end;

procedure TCICIdIRC.CommandTOPIC(ASender: TIdCommand);
var
  LChannel, LTopic: String;
begin
  if Assigned(FOnTopic) then
  begin
    if ASender.Params.Count > 0 then begin
      LChannel := ASender.Params[0];
    end;
    if (ASender.CommandHandler.Command <> '331') and (ASender.Params.Count > 1) then begin {do not localize}
      LTopic := ASender.Params[1];
    end else begin
      LTopic := '';
    end;
    OnTopic(ASender.Context, FSenderNick, FSenderHost, LChannel, LTopic);
  end;
end;

procedure TCICIdIRC.CommandWELCOME(ASender: TIdCommand);
var
  LTmp: string;
begin
  if Assigned(FOnSWelcome) then begin
    LTmp := ASender.UnparsedParams;
    OnServerWelcome(ASender.Context, FetchIRCParam(LTmp));
  end;
end;

procedure TCICIdIRC.CommandERROR(ASender: TIdCommand);
var
  LTmp: String;
begin
  if Assigned(FOnServerError) then begin
    LTmp := ASender.UnparsedParams;
    OnServerError(ASender.Context, 0, FetchIRCParam(LTmp));
  end;
end;

procedure TCICIdIRC.CommandYOURHOST(ASender: TIdCommand);
var
  LTmp: String;
begin
  if Assigned(FOnYourHost) then begin
    LTmp := ASender.UnparsedParams;
    OnYourHost(ASender.Context, FetchIRCParam(LTmp));
  end;
end;

procedure TCICIdIRC.CommandCREATED(ASender: TIdCommand);
var
  LTmp: string;
begin
  if Assigned(FOnSCreated) then begin
    LTmp := ASender.UnparsedParams;
    OnServerCreated(ASender.Context, FetchIRCParam(LTmp));
  end;
end;

procedure TCICIdIRC.CommandMYINFO(ASender: TIdCommand);
var
  LTmp, LServer, LVersion, LUserModes, LChanModes: String;
begin
  if Assigned(FOnMyInfo) then begin
    LTmp := ASender.UnparsedParams;
    LServer := FetchIRCParam(LTmp);
    LVersion := FetchIRCParam(LTmp);
    LUserModes := FetchIRCParam(LTmp);
    LChanModes := FetchIRCParam(LTmp);
    // TODO: <channel_modes_with_params> <user_modes_with_params> <server_modes> <server_modes_with_params>
    OnMyInfo(ASender.Context, LServer, LVersion, LUserModes, LChanModes, LTmp);
  end;
end;

procedure TCICIdIRC.DoBounce(ASender: TIdCommand; ALegacy: Boolean);
var
  LHost, LPort, LInfo: string;
begin
  if Assigned(FOnBounce) then begin
    if ALegacy then begin
      LInfo := ASender.Params[0];
      LHost := FetchIRCParam(LInfo);
      LPort := FetchIRCParam(LInfo);
    end else
    begin
      LHost := ASender.Params[0];
      LPort := ASender.Params[1];
      if ASender.Params.Count > 2 then begin
        LInfo := ASender.Params[2];
      end;
    end;
    // TODO: reconnect automatically
    OnBounce(ASender.Context, LHost, IndyStrToInt(LPort, 0), LInfo);
  end;
end;

procedure TCICIdIRC.CommandISUPPORT(ASender: TIdCommand);
var
  LParams: TStringList;
  I: Integer;
begin
  if ASender.Params.Count = 1 then begin
    DoBounce(ASender, True); // legacy, deprecated
    Exit;
  end;
  if Assigned(FOnISupport) then
  begin
    LParams := TStringList.Create;
    try
      for I := 1 to ASender.Params.Count-1 do // skip nickname
      begin
        LParams.Add(ASender.Params[I]);
      end;
      OnISupport(ASender.Context, LParams);
    finally
      LParams.Free;
    end;
  end;
end;

procedure TCICIdIRC.CommandBOUNCE(ASender: TIdCommand);
begin
  DoBounce(ASender, False);
end;

procedure TCICIdIRC.CommandAWAY(ASender: TIdCommand);
var
  LCmd: Integer;
begin
  LCmd := IndyStrToInt(ASender.CommandHandler.Command, 0);
  case LCmd of
    301:
    begin
      if Assigned(FOnAway) then begin
        OnAway(ASender.Context, FSenderNick, FSenderHost, ASender.Params[0], True);
      end;
    end;
    305, 306:
    begin
      FUserAway := (LCmd = 306);
      if Assigned(FOnAway) then begin
        OnAway(ASender.Context, GetUsedNickname, '', ASender.Params[0], FUserAway);
      end;
    end;
  end;
end;

procedure TCICIdIRC.CommandUSERHOST(ASender: TIdCommand);
begin
  if Assigned(FOnUserInfo) then begin
    OnUserInfoReceived(ASender.Context, ASender.UnparsedParams);
  end;
end;

procedure TCICIdIRC.CommandISON(ASender: TIdCommand);
begin
  if Assigned(FOnIsOnIRC) then begin
    OnIsOnIRC(ASender.Context, FSenderNick, FSenderHost);
  end;
end;

procedure TCICIdIRC.CommandWHOIS(ASender: TIdCommand);
begin
  if not Assigned(FWhoIs) then begin
    FWhoIs := TStringList.Create;
  end;
  FWhoIs.Add(ASender.Params[0]);
end;

procedure TCICIdIRC.CommandENDOFWHOIS(ASender: TIdCommand);
begin
  CommandWHOIS(ASender);
  if Assigned(FOnWhoIs) then begin
    OnWhoIs(ASender.Context, FWhoIs);
  end;
  FWhoIs.Clear;
end;

procedure TCICIdIRC.CommandWHOWAS(ASender: TIdCommand);
begin
  if not Assigned(FWhoWas) then begin
    FWhoWas := TStringList.Create;
  end;
  FWhoWas.Add(ASender.Params[0]);
end;

procedure TCICIdIRC.CommandENDOFWHOWAS(ASender: TIdCommand);
begin
  CommandWHOWAS(ASender);
  if Assigned(FOnWhoWas) then begin
    OnWhoWas(ASender.Context, FWhoWas);
  end;
  FWhoWas.Clear;
end;

procedure TCICIdIRC.CommandLISTSTART(ASender: TIdCommand);
begin
  if not Assigned(FSvrList) then begin
    FSvrList := TStringList.Create;
  end else begin
    FSvrList.Clear;
  end;
end;

procedure TCICIdIRC.CommandLIST(ASender: TIdCommand);
begin
  if not Assigned(FSvrList) then begin
    FSvrList := TStringList.Create;
  end;
  FSvrList.Add(ASender.Params[0] + ' ' + ASender.Params[1] + ' ' + ASender.Params[2]); {do not localize}
end;

procedure TCICIdIRC.CommandLISTEND(ASender: TIdCommand);
begin
  CommandLIST(ASender);
  if Assigned(FOnSvrList) then begin
    OnServerListReceived(ASender.Context, FSvrList);
  end;
  FSvrList.Clear;
end;

procedure TCICIdIRC.CommandINVITING(ASender: TIdCommand);
begin
  if Assigned(FOnInviting) then begin
    OnInviting(ASender.Context, FSenderNick, FSenderHost);
  end;
end;

procedure TCICIdIRC.CommandSUMMONING(ASender: TIdCommand);
begin
  if Assigned(FOnSummon) then begin
    OnSummon(ASender.Context, FSenderNick, FSenderHost);
  end;
end;

procedure TCICIdIRC.CommandINVITELIST(ASender: TIdCommand);
begin
  if not Assigned(FInvites) then begin
    FInvites := TStringList.Create;
  end;
  // TODO: use a collection instead
  FInvites.Add(ASender.Params[0] + ' ' + ASender.Params[1]); {do not localize}
end;

procedure TCICIdIRC.CommandENDOFINVITELIST(ASender: TIdCommand);
begin
  if not Assigned(FInvites) then begin
    FInvites := TStringList.Create;
  end;
  FInvites.Add(ASender.Params[0]);
  if Assigned(FOnINVList) then begin
    OnInvitationListReceived(ASender.Context, FSenderNick, FInvites);
  end;
  FInvites.Clear;
end;

procedure TCICIdIRC.CommandEXCEPTLIST(ASender: TIdCommand);
begin
  if not Assigned(FExcepts) then begin
    FExcepts := TStringList.Create;
  end;
  // TODO: use a collection instead
  FExcepts.Add(ASender.Params[0] + ' ' + ASender.Params[1]); {do not localize}
end;

procedure TCICIdIRC.CommandENDOFEXCEPTLIST(ASender: TIdCommand);
begin
  if not Assigned(FExcepts) then begin
    FExcepts := TStringList.Create;
  end;
  FExcepts.Add(ASender.Params[0]);
  if Assigned(FOnEXCList) then begin
    OnExceptionListReceived(ASender.Context, FSenderNick, FExcepts);
  end;
  FExcepts.Clear;
end;

procedure TCICIdIRC.CommandWHOREPLY(ASender: TIdCommand);
begin
  if not Assigned(FWho) then begin
    FWho := TStringList.Create;
  end;
  FWho.Add(''); // TODO
end;

procedure TCICIdIRC.CommandENDOFWHO(ASender: TIdCommand);
begin
  if not Assigned(FWho) then begin
    FWho := TStringList.Create;
  end;
  FWho.Add(ASender.Params[0]);
  if Assigned(FOnWho) then begin
    OnWho(ASender.Context, FWho);
  end;
  FWho.Clear;
end;

procedure TCICIdIRC.CommandNAMEREPLY(ASender: TIdCommand);
var
  i: Integer;
  LNames: string;
  LNameList: TStringList;
begin
  if not Assigned(FNames) then begin
    FNames := TStringList.Create;
  end;
  // AWinkelsdorf 3/10/2010 Rewrote logic to split Names into single Lines of FNames
  if ASender.Params.Count >= 4 then begin // Names are in [3]
    LNames := StringsReplace(ASender.Params[3], [' '], [',']); {do not localize}
    LNameList := TStringList.Create;
    try
      LNameList.CommaText := LNames;
      for i := 0 to LNameList.Count - 1 do
      begin
        if LNameList[i] <> '' then
          FNames.Add(LNameList[i]);
      end;
    finally
      LNameList.Free;
    end;
  end else begin
    FNames.Add(ASender.Params[0]);
  end;
end;

procedure TCICIdIRC.CommandENDOFNAMES(ASender: TIdCommand);
var
  LChannel: string;
begin
  if not Assigned(FNames) then begin
    FNames := TStringList.Create;
  end;
  LChannel := '';
  if ASender.Params.Count > 0 then begin
    LChannel := ASender.Params[1];
  end;
  if Assigned(FOnNickList) then begin
    OnNicknamesListReceived(ASender.Context, LChannel, FNames);
  end;
  FNames.Clear;
end;

procedure TCICIdIRC.CommandLINKS(ASender: TIdCommand);
var
  LHopCnt, LInfo: String;
begin
  if not Assigned(FLinks) then begin
    FLinks := TStringList.Create;
  end;
  LInfo := ASender.Params[2];
  LHopCnt := Fetch(LInfo);
  // TODO: use a collection instead
  FLinks.Add(ASender.Params[0] + ' ' + ASender.Params[1] + ' ' + LHopCnt + ' ' + LInfo); {do not localize}
end;

procedure TCICIdIRC.CommandENDOFLINKS(ASender: TIdCommand);
begin
  if not Assigned(FLinks) then begin
    FLinks := TStringList.Create;
  end;
  FLinks.Add(ASender.Params[0]);
  if Assigned(FOnKnownSvrs) then begin
    OnKnownServersListReceived(ASender.Context, FLinks);
  end;
  FLinks.Clear;
end;

procedure TCICIdIRC.CommandBANLIST(ASender: TIdCommand);
begin
  if not Assigned(FBans) then begin
    FBans := TStringList.Create;
  end;
  // TODO: use a collection instead
  FBans.Add(ASender.Params[0] + ' ' + ASender.Params[1]); {do not localize}
end;

procedure TCICIdIRC.CommandENDOFBANLIST(ASender: TIdCommand);
begin
  if not Assigned(FBans) then begin
    FBans := TStringList.Create;
  end;
  FBans.Add(ASender.Params[0]);
  if Assigned(FOnBanList) then begin
    OnBanListReceived(ASender.Context, FSenderNick, FBans);
  end;
  FBans.Clear;
end;

procedure TCICIdIRC.CommandINFO(ASender: TIdCommand);
begin
  // TODO
end;

procedure TCICIdIRC.CommandENDOFINFO(ASender: TIdCommand);
begin
  if Assigned(FOnUserInfo) then begin
    OnUserInfoReceived(ASender.Context, ASender.UnparsedParams);
  end;
end;

procedure TCICIdIRC.CommandMOTD(ASender: TIdCommand);
begin
  if not Assigned(FMotd) then begin
    FMotd := TStringList.Create;
  end;
  FMotd.Add(ASender.Params[0]);
end;

procedure TCICIdIRC.CommandENDOFMOTD(ASender: TIdCommand);
begin
  if not Assigned(FMotd) then begin
    FMotd := TStringList.Create;
  end;
  if Assigned(FOnMOTD) then begin
    OnMOTD(ASender.Context, FMotd);
  end;
  FMotd.Clear;
end;

procedure TCICIdIRC.CommandREHASHING(ASender: TIdCommand);
begin
  if Assigned(FOnRehash) then begin
    OnRehash(ASender.Context, FSenderNick, FSenderHost);
  end;
end;

procedure TCICIdIRC.CommandUSERSSTART(ASender: TIdCommand);
begin
  if not Assigned(FUsers) then begin
    FUsers := TStringList.Create;
  end else begin
    FUsers.Clear;
  end;
end;

procedure TCICIdIRC.CommandUSERS(ASender: TIdCommand);
begin
  if ASender.CommandHandler.Command = '393' then {do not localize}
  begin
    if not Assigned(FUsers) then begin
      FUsers := TStringList.Create;
    end;
    // TODO: use a collection instead
    FUsers.Add(ASender.Params[0] + ' ' + ASender.Params[1] + ' ' + ASender.Params[2]); {do not localize}
  end;
end;

procedure TCICIdIRC.CommandENDOFUSERS(ASender: TIdCommand);
begin
  if not Assigned(FUsers) then begin
    FUsers := TStringList.Create;
  end;
  if Assigned(FOnSvrUsers) then begin
    OnServerUsersListReceived(ASender.Context, FUsers);
  end;
  FUsers.Clear;
end;

procedure TCICIdIRC.CommandENDOFSTATS(ASender: TIdCommand);
begin
  if Assigned(FOnSvrStats) then begin
    OnServerStatsReceived(ASender.Context, nil); // TODO
  end;
end;

procedure TCICIdIRC.CommandSERVLIST(ASender: TIdCommand);
begin
  // <name> <server> <mask> <type> <hopcount> <info>
end;

procedure TCICIdIRC.CommandSERVLISTEND(ASender: TIdCommand);
begin
  // <mask> <type> :<info>
end;

procedure TCICIdIRC.CommandTIME(ASender: TIdCommand);
var
  LServer, LTimeString: String;
begin
  if Assigned(FOnSvrTime) then begin
    LServer := ASender.Params[0];
    case ASender.Params.Count of
      2: begin // "<server> :<time string>"
        LTimeString := ASender.Params[1];
        end;
      4: begin // "<server> <timestamp> <offset> :<time string>" or "<server> <timezone name> <microseconds> :<time string>"
        LTimeString := IndyFormat('%s %s %s', [ASender.Params[1], ASender.Params[2], ASender.Params[3]]); {do not localize}
        end;
      7: begin // "<server> <year> <month> <day> <hour> <minute> <second>"
        LTimeString := IndyFormat('%s %s %s %s %s %s', {do not localize}
          [ASender.Params[1], ASender.Params[2], ASender.Params[3],
          ASender.Params[4], ASender.Params[5], ASender.Params[6]]);
        end;
    end;
    OnServerTime(ASender.Context, LServer, LTimeString);
  end;
end;

procedure TCICIdIRC.CommandSERVICE(ASender: TIdCommand);
begin
  if Assigned(FOnService) then begin
    OnService(ASender.Context);
  end;
end;

procedure TCICIdIRC.CommandVERSION(ASender: TIdCommand);
begin
  if Assigned(FOnSvrVersion) then begin
    OnServerVersion(ASender.Context, ASender.Params[0], ASender.Params[1], ASender.Params[2]);
  end;
end;

procedure TCICIdIRC.CommandCHANMODE(ASender: TIdCommand);
begin
  if Assigned(FOnChanMode) then begin
    OnChannelMode(ASender.Context, FSenderNick, FSenderHost, ASender.Params[0], ASender.Params[1], ASender.Params[2]);
  end;
end;

procedure TCICIdIRC.CommandOPER(ASender: TIdCommand);
begin
  if Assigned(FOnOp) then begin
    OnOp(ASender.Context, FSenderNick, FSenderHost, ASender.Params[0]);
  end;
end;

procedure TCICIdIRC.CommandNICKINUSE(ASender: TIdCommand);
begin
  if ASender.CommandHandler.Command = '433' then {do not localize}
  begin
    if (Connected) and (FAltNickname <> '') then
    begin
      if not FAltNickUsed then begin
        // try only once using AltNickname (FAltNickUsed is cleared in .Connect)
        SetNickname(FAltNickname);
        FAltNickUsed := True;
      end
      else
      begin
        // already tried to use AltNickName...
        if Assigned(FOnNickError) then begin
          OnNicknameError(ASender.Context, IndyStrToInt(ASender.CommandHandler.Command));
        end;
      end;
    end;
  end;
end;

procedure TCICIdIRC.ParseDCC(AContext: TIdContext; const ADCC: String);
const
  IdIRCDCC: array[0..3] of String = ('SEND', 'CHAT', 'RESUME', 'ACCEPT');  {do not localize}
var
  LTmp, LType, LArg, LAddr, LPort, LSize: String;
begin
  LTmp := ADCC;
  LType := FetchIRCParam(LTmp);
  LArg := FetchIRCParam(LTmp);
  LAddr := FetchIRCParam(LTmp);
  LPort := FetchIRCParam(LTmp);
  LSize := FetchIRCParam(LTmp);
  //
  case PosInStrArray(LType, IdIRCDCC) of
    0: {SEND}
      begin
        if Assigned(FOnDCCSend) then begin
          FOnDCCSend(AContext, FSenderNick, LAddr, ExtractFileName(LArg), IndyStrToInt(LPort), IndyStrToInt64(LSize));
        end;
      end;
    1: {CHAT}
      begin
        if Assigned(FOnDCCChat) then begin
          FOnDCCChat(AContext, FSenderNick, LAddr, IndyStrToInt(LPort));
        end;
      end;
    2: {RESUME}
      begin
        if Assigned(FOnDCCResume) then begin
          FOnDCCResume(AContext, FSenderNick, LAddr, LArg, IndyStrToInt(LPort), IndyStrToInt64(LSize));
        end;
      end;
    3: {ACCEPT}
      begin
        if Assigned(FOnDCCAccept) then begin
          FOnDCCAccept(AContext, FSenderNick, LAddr, LArg, IndyStrToInt(LPort), IndyStrToInt64(LSize));
        end;
      end;
  end;
end;

function TCICIdIRC.GetUsedNickname: String;
begin
  if FAltNickUsed then
    Result := FAltNickname
  else
    Result := FNickname;
end;

procedure TCICIdIRC.SetNickname(const AValue: String);
begin
  if not Connected then begin
    FNickname := AValue;
  end else begin
    Raw(IndyFormat('NICK %s', [AValue])); {do not localize}
  end;
end;

procedure TCICIdIRC.SetUsername(const AValue: String);
begin
  if not Connected then begin
    FUsername := AValue;
  end else begin
    Raw(IndyFormat('USER %s %s %s :%s', [AValue, GetUserMode, '*', FRealName]));  {do not localize}
  end;
end;

procedure TCICIdIRC.SeTCICIdIRCUserMode(AValue: TCICIdIRCUserModes);
begin
  FUserMode := AValue;
end;

procedure TCICIdIRC.SeTCICIdIRCReplies(AValue: TCICIdIRCReplies);
begin
  FReplies.Assign(AValue);
end;

function TCICIdIRC.IsChannel(const AChannel: String): Boolean;
begin
  //Result := (Length(AChannel) > 0) and CharIsInSet(AChannel, 1, ['&','#','+','!']);  {do not localize}
  Result := CharIsInSet(AChannel, 1, '&#+!');  {do not localize}
end;

function TCICIdIRC.IsOp(const ANickname: String): Boolean;
begin
  Result := TextStartsWith(ANickname, '@');
end;

function TCICIdIRC.IsVoice(const ANickname: String): Boolean;
begin
  Result := TextEndsWith(ANickname, '+');
end;

procedure TCICIdIRC.Say(const ATarget, AMsg: String);
begin
  Raw(IndyFormat('PRIVMSG %s :%s', [ATarget, AMsg])); {do not localize}
end;

procedure TCICIdIRC.Notice(const ATarget, AMsg: String);
begin
  Raw(IndyFormat('NOTICE %s :%s', [ATarget, AMsg]));  {do not localize}
end;

procedure TCICIdIRC.Action(const ATarget, AMsg: String);
begin
  CTCPQuery(ATarget, 'ACTION', AMsg); {do not localize}
end;

procedure TCICIdIRC.CTCPQuery(const ATarget, ACommand, AParameters: String);
var
  LTmp: String;
begin
  LTmp := UpperCase(ACommand);
  if AParameters <> '' then begin
    LTmp := LTmp + ' ' + AParameters;
  end;
  Say(ATarget, XDelim+CTCPQuote(LTmp)+XDelim);  {do not localize}
end;

procedure TCICIdIRC.CTCPReply(const ATarget, ACTCP, AReply: String);
var
  LTmp: String;
begin
  LTmp := ACTCP;
  if AReply <> '' then begin
    LTmp := LTmp + ' ' + AReply;
  end;
  Notice(ATarget, XDelim+CTCPQuote(LTmp)+XDelim); {do not localize}
end;

procedure TCICIdIRC.Join(const AChannel: String; const AKey: String = '');
begin
  if IsChannel(AChannel) then begin
    if AKey <> '' then begin
      Raw(IndyFormat('JOIN %s %s', [AChannel, AKey])) {do not localize}
    end else begin
      Raw(IndyFormat('JOIN %s', [AChannel])); {do not localize}
    end;
  end;
end;

procedure TCICIdIRC.Part(const AChannel: String; const AReason: String = '');
begin
  if IsChannel(AChannel) then begin
    if AReason <> '' then begin
      Raw(IndyFormat('PART %s :%s', [AChannel, AReason])) {do not localize}
    end else begin
      Raw(IndyFormat('PART %s', [AChannel])); {do not localize}
    end;
  end;
end;

procedure TCICIdIRC.Kick(const AChannel, ANickname: String; const AReason: String = '');
begin
  if IsChannel(AChannel) then begin
    if AReason <> '' then begin
      Raw(IndyFormat('KICK %s %s :%s', [AChannel, ANickname, AReason]));  {do not localize}
    end else begin
      Raw(IndyFormat('KICK %s %s', [AChannel, ANickname]));  {do not localize}
    end;
  end;
end;

procedure TCICIdIRC.SetChannelMode(const AChannel, AMode: String; const AParams: String = '');
begin
  if IsChannel(AChannel) then begin
    if AParams = '' then begin
      Raw(IndyFormat('MODE %s %s', [AChannel, AMode])); {do not localize}
    end else begin
      Raw(IndyFormat('MODE %s %s %s', [AChannel, AMode, AParams])); {do not localize}
    end;
  end;
end;

procedure TCICIdIRC.SetUserMode(const ANickname, AMode: String);
begin
  Raw(IndyFormat('MODE %s %s', [ANickname, AMode]));  {do not localize}
end;

procedure TCICIdIRC.GetChannelTopic(const AChannel: String);
begin
  if IsChannel(AChannel) then begin
    Raw(IndyFormat('TOPIC %s', [AChannel]));  {do not localize}
  end;
end;

procedure TCICIdIRC.SetChannelTopic(const AChannel, ATopic: String);
begin
  if IsChannel(AChannel) then begin
    Raw(IndyFormat('TOPIC %s :%s', [AChannel, ATopic]));  {do not localize}
  end;
end;

procedure TCICIdIRC.SetAway(const AMsg: String);
begin
  if AMsg <> '' then begin
    Raw(IndyFormat('AWAY :%s', [AMsg])); {do not localize}
  end else begin
    Raw('AWAY');  {do not localize}
  end;
end;

procedure TCICIdIRC.Op(const AChannel, ANickname: String);
begin
  SetChannelMode(AChannel, '+o', ANickname);  {do not localize}
end;

procedure TCICIdIRC.Deop(const AChannel, ANickname: String);
begin
  SetChannelMode(AChannel, '-o', ANickname);  {do not localize}
end;

procedure TCICIdIRC.Voice(const AChannel, ANickname: String);
begin
  SetChannelMode(AChannel, '+v', ANickname);  {do not localize}
end;

procedure TCICIdIRC.Devoice(const AChannel, ANickname: String);
begin
  SetChannelMode(AChannel, '-v', ANickname);  {do not localize}
end;

procedure TCICIdIRC.Ban(const AChannel, AHostmask: String);
begin
  SetChannelMode(AChannel, '+b', AHostmask);  {do not localize}
end;

procedure TCICIdIRC.Unban(const AChannel, AHostmask: String);
begin
  SetChannelMode(AChannel, '-b', AHostmask);  {do not localize}
end;

procedure TCICIdIRC.RegisterService(const ANickname, ADistribution, AInfo: String; AType: Integer);
begin
  Raw(IndyFormat('SERVICE %s * %s %s 0 :%s',  {do not localize}
    [ANickname, ADistribution, AType, AInfo]));
end;

procedure TCICIdIRC.ListChannelNicknames(const AChannel: String; const ATarget: String = '');
begin
  if IsChannel(AChannel) then begin
    if ATarget <> '' then begin
      Raw(IndyFormat('NAMES %s %s', [AChannel, ATarget]));  {do not localize}
    end else begin
      Raw(IndyFormat('NAMES %s', [AChannel]));  {do not localize}
    end;
  end;
end;

procedure TCICIdIRC.ListChannel(const AChannel: String; const ATarget: String = '');
begin
  if IsChannel(AChannel) then begin
    if ATarget <> '' then begin
      Raw(IndyFormat('LIST %s %s', [AChannel, ATarget])); {do not localize}
    end else begin
      Raw(IndyFormat('LIST %s', [AChannel])); {do not localize}
    end;
  end;
end;

procedure TCICIdIRC.Invite(const ANickname, AChannel: String);
begin
  if IsChannel(AChannel) then begin
    Raw(IndyFormat('INVITE %s %s', [ANickname, AChannel])); {do not localize}
  end;
end;

procedure TCICIdIRC.GetMessageOfTheDay(const ATarget: String = '');
begin
  if ATarget <> '' then begin
    Raw(IndyFormat('MOTD %s', [ATarget]));  {do not localize}
  end else begin
    Raw('MOTD');  {do not localize}
  end;
end;

procedure TCICIdIRC.GetNetworkStatus(const AHostMask: String = ''; const ATarget: String = '');
begin
  if (AHostMask = '') and (ATarget = '') then begin
    Raw('LUSERS');  {do not localize}
  end
  else if AHostMask = '' then begin
    Raw(IndyFormat('LUSERS %s', [ATarget]));  {do not localize}
  end
  else if ATarget = '' then begin
    Raw(IndyFormat('LUSERS %s', [AHostMask]));  {do not localize}
  end else begin
    Raw(IndyFormat('LUSERS %s %s', [AHostMask, ATarget]));  {do not localize}
  end;
end;

procedure TCICIdIRC.GetServerVersion(const ATarget: String = '');
begin
  if ATarget = '' then begin
    Raw('VERSION'); {do not localize}
  end else begin
    Raw(IndyFormat('VERSION %s', [ATarget])); {do not localize}
  end;
end;

procedure TCICIdIRC.GetServerStatus(AQuery: TCICIdIRCStat; const ATarget: String = '');
const
  IdIRCStatChars: array[TCICIdIRCStat] of Char = ('l', 'm', 'o', 'u'); {do not localize}
begin
  if ATarget <> '' then begin
    Raw(IndyFormat('STATS %s %s', [IdIRCStatChars[AQuery], ATarget])); {do not localize}
  end else begin
    Raw(IndyFormat('STATS %s', [IdIRCStatChars[AQuery]])); {do not localize}
  end;
end;

procedure TCICIdIRC.ListKnownServerNames(const ARemoteHost: String = ''; const AHostMask: String = '');
begin
  if (ARemoteHost = '') and (AHostMask = '') then begin
    Raw('LINKS'); {do not localize}
  end
  else if ARemoteHost = '' then begin
    Raw(IndyFormat('LINKS %s', [AHostMask])); {do not localize}
  end else begin
    Raw(IndyFormat('LINKS %s %s', [ARemoteHost, AHostMask])); {do not localize}
  end;
end;

procedure TCICIdIRC.QueryServerTime(const ATarget: String = '');
begin
  if ATarget <> '' then begin
    Raw(IndyFormat('TIME %s', [ATarget]));  {do not localize}
  end else begin
    Raw('TIME');  {do not localize}
  end;
end;

procedure TCICIdIRC.RequestServerConnect(const ATargetHost: String; APort: Integer;
  const ARemoteHost: String = '');
begin
  if ARemoteHost <> '' then begin
    Raw(IndyFormat('CONNECT %s %d %s', [ATargetHost, APort, ARemoteHost])); {do not localize}
  end else begin
    Raw(IndyFormat('CONNECT %s %d', [ATargetHost, APort])); {do not localize}
  end;
end;

procedure TCICIdIRC.TraceServer(const ATarget: String = '');
begin
  if ATarget <> '' then begin
    Raw(IndyFormat('TRACE %s', [ATarget])); {do not localize}
  end else begin
    Raw('TRACE'); {do not localize}
  end;
end;

procedure TCICIdIRC.GetAdminInfo(const ATarget: String = '');
begin
  if ATarget <> '' then begin
    Raw(IndyFormat('ADMIN %s', [ATarget])); {do not localize}
  end else begin
    Raw('ADMIN'); {do not localize}
  end;
end;

procedure TCICIdIRC.GetServerInfo(const ATarget: String = '');
begin
  if ATarget <> '' then begin
    Raw(IndyFormat('INFO %s', [ATarget]));  {do not localize}
  end else begin
    Raw('INFO');  {do not localize}
  end;
end;

procedure TCICIdIRC.ListNetworkServices(const AHostMask: String = ''; const AType: String = '');
begin
  if (AHostMask = '') and (AType = '') then begin
    Raw('SERVLIST');  {do not localize}
  end
  else if AType <> '' then begin
    Raw(IndyFormat('SERVLIST %s %s', [AHostMask, AType]));  {do not localize}
  end else begin
    Raw(IndyFormat('SERVLIST %s', [AHostMask]));  {do not localize}
  end;
end;

procedure TCICIdIRC.QueryService(const AServiceName, AMessage: String);
begin
  Raw(IndyFormat('SQUERY %s :%s', [AServiceName, AMessage]));  {do not localize}
end;

procedure TCICIdIRC.Who(const AMask: String; AOnlyAdmins: Boolean);
begin
  if (AMask = '') and (not AOnlyAdmins) then begin
    Raw('WHO'); {do not localize}
  end
  else if AOnlyAdmins then begin
    Raw(IndyFormat('WHO %s o', [AMask])); {do not localize}
  end else begin
    Raw(IndyFormat('WHO %s', [AMask])); {do not localize}
  end;
end;

procedure TCICIdIRC.WhoIs(const AMask: String; const ATarget: String = '');
begin
  if ATarget <> '' then begin
    Raw(IndyFormat('WHOIS %s %s', [ATarget, AMask])); {do not localize}
  end else begin
    Raw(IndyFormat('WHOIS %s', [AMask])); {do not localize}
  end;
end;

procedure TCICIdIRC.WhoWas(const ANickname: String; ACount: Integer = -1; const ATarget: String = '');
begin
  if (ATarget = '') and (ACount < 0) then begin
    Raw(IndyFormat('WHOWAS %s', [ANickname]));  {do not localize}
  end
  else if ATarget <> '' then begin
    Raw(IndyFormat('WHOWAS %s %d %s', [ANickname, ACount, ATarget]));  {do not localize}
  end
  else begin
    Raw(IndyFormat('WHOWAS %s %d', [ANickname, ACount])); {do not localize}
  end;
end;

procedure TCICIdIRC.Kill(const ANickname, AComment: String);
begin
  Raw(IndyFormat('KILL %s :%s', [ANickname, AComment])); {do not localize}
end;

procedure TCICIdIRC.Ping(const AServer1: String; const AServer2: String = '');
begin
  if AServer2 <> '' then begin
    Raw(IndyFormat('PING %s %s', [AServer1, AServer2]));  {do not localize}
  end else begin
    Raw(IndyFormat('PING %s', [AServer1])); {do not localize}
  end;
end;

procedure TCICIdIRC.Pong(const AServer1: String; const AServer2: String = '');
begin
  if AServer2 <> '' then begin
    Raw(IndyFormat('PONG %s %s', [AServer1, AServer2]));  {do not localize}
  end else begin
    Raw(IndyFormat('PONG %s', [AServer1])); {do not localize}
  end;
end;

procedure TCICIdIRC.Error(const AMessage: String);
begin
  Raw(IndyFormat('ERROR :%s', [AMessage]));  {do not localize}
end;

procedure TCICIdIRC.ReHash;
begin
  Raw('REHASH');  {do not localize}
end;

procedure TCICIdIRC.Die;
begin
  Raw('DIE'); {do not localize}
end;

procedure TCICIdIRC.Restart;
begin
  Raw('RESTART'); {do not localize}
end;

procedure TCICIdIRC.Summon(const ANickname: String; const ATarget: String = '';
  const AChannel: String = '');
begin
  if (ATarget = '') and (AChannel = '') then begin
    Raw(IndyFormat('SUMMON %s', [ANickname]));  {do not localize}
  end
  else if AChannel <> '' then
  begin
    if IsChannel(AChannel) then begin
      Raw(IndyFormat('SUMMON %s %s %s', [ANickname, ATarget, AChannel])); {do not localize}
    end;
  end else begin
    Raw(IndyFormat('SUMMON %s %s', [ANickname, ATarget]));  {do not localize}
  end;
end;

procedure TCICIdIRC.ListServerUsers(const ATarget: String = '');
begin
  if ATarget <> '' then begin
    Raw(IndyFormat('USERS %s', [ATarget])); {do not localize}
  end else begin
    Raw('USERS'); {do not localize}
  end;
end;

procedure TCICIdIRC.SayWALLOPS(const AMessage: String);
begin
  Raw(IndyFormat('WALLOPS :%s', [AMessage]));  {do not localize}
end;

procedure TCICIdIRC.GetUserInfo(const ANickname: String);
begin
  Raw(IndyFormat('USERHOST %s', [ANickname]));  {do not localize}
end;

procedure TCICIdIRC.GetUsersInfo(const ANicknames: array of string);
var
  I: Integer;
  S: string;
begin
  if Length(ANicknames) > 0 then
  begin
    for I := Low(ANicknames) to High(ANicknames) do begin
      S := S + ' ' + ANicknames[I]; {do not localize}
    end;
    Raw(IndyFormat('USERHOST%s', [S]));  {do not localize}
  end;
end;

procedure TCICIdIRC.IsOnIRC(const ANickname: String);
begin
  Raw(IndyFormat('ISON %s', [ANickname]));  {do not localize}
end;

procedure TCICIdIRC.IsOnIRC(const ANicknames: array of String);
var
  I: Integer;
  S: string;
begin
  if Length(ANicknames) > 0 then
  begin
    for I := Low(ANicknames) to High(ANicknames) do begin
      S := S + ' ' + ANicknames[I]; {do not localize}
    end;
    Raw(IndyFormat('ISON%s', [S]));  {do not localize}
  end;
end;

procedure TCICIdIRC.BecomeOp(const ANickname, APassword: String);
begin
  Raw(IndyFormat('OPER %s %s', [ANickname, APassword]));  {do not localize}
end;

procedure TCICIdIRC.SQuit(const AHost, AComment: String);
begin
  Raw(IndyFormat('SQUIT %s :%s', [AHost, AComment]));  {do not localize}
end;

procedure TCICIdIRC.SetChannelLimit(const AChannel: String; ALimit: Integer);
begin
  SetChannelMode(AChannel, '+l', IntToStr(ALimit)); {do not localize}
end;

procedure TCICIdIRC.SetChannelKey(const AChannel, AKey: String);
begin
  SetChannelMode(AChannel, '+k', AKey); {do not localize}
end;

procedure TCICIdIRC.SetIOHandler(AValue: TIdIOHandler);
begin
  inherited SetIOHandler(AValue);
  //we do it this way so that if a user is using a custom value <> default, the port
  //is not set when the IOHandler is changed.
  if (IOHandler is TIdSSLIOHandlerSocketBase) then begin
    if Port = IdPORT_IRC then begin
      Port := IdPORT_IRCS;
    end;
  end else begin
    if Port = IdPORT_IRCS then begin
      Port := IdPORT_IRC;
    end;
  end;
end;

end.


