home *** CD-ROM | disk | FTP | other *** search
/ ftp.swcp.com / ftp.swcp.com.zip / ftp.swcp.com / mac / mozilla-mac-0.9.sea.hqx / mozilla-mac-0.9 / Chrome / chatzilla.jar / content / chatzilla / lib / js / irc.js < prev    next >
Text File  |  2001-05-05  |  48KB  |  2,058 lines

  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  2.  *   
  3.  * The contents of this file are subject to the Mozilla Public
  4.  * License Version 1.1 (the "License"); you may not use this file
  5.  * except in compliance with the License. You may obtain a copy of
  6.  * the License at http://www.mozilla.org/MPL/
  7.  *
  8.  * Software distributed under the License is distributed on an "AS
  9.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  10.  * implied. See the License for the specific language governing
  11.  * rights and limitations under the License.
  12.  *
  13.  * The Original Code is JSIRC Library
  14.  *
  15.  * The Initial Developer of the Original Code is New Dimensions Consulting,
  16.  * Inc. Portions created by New Dimensions Consulting, Inc. are
  17.  * Copyright (C) 1999 New Dimenstions Consulting, Inc. All
  18.  * Rights Reserved.
  19.  *
  20.  * Contributor(s): 
  21.  *
  22.  *
  23.  * Contributor(s):
  24.  *  Robert Ginda, rginda@ndcico.com, original author
  25.  *
  26.  ****
  27.  *
  28.  * depends on utils.js, events.js, and connection.js
  29.  *
  30.  * IRC(RFC 1459) library.
  31.  * Contains the following classes:
  32.  *
  33.  * CIRCNetwork
  34.  * Networtk object.  Takes care of logging into a "primary" server given a
  35.  * list of potential hostnames for an IRC network.  (among other things.)
  36.  *
  37.  * CIRCServer
  38.  * Server object.  Requires an initialized bsIConnection object for
  39.  * communicating with the irc server.
  40.  * Server.sayTo queues outgoing PRIVMSGs for sending to the server.  Using
  41.  * sayTo takes care not to send lines faster than one every 1.5 seconds.
  42.  * Server.connection.sendData sends raw lines over the connection, avoiding the
  43.  * queue.
  44.  *
  45.  * CIRCUser
  46.  * User objects.  Children of server objects.
  47.  *
  48.  * CIRCChannel
  49.  * Channel object.  Children of server objects.
  50.  *
  51.  * CIRCChanMode
  52.  * Channel mode object.  Children of channel objects
  53.  *
  54.  * CIRCChanUser
  55.  * Channel User objects.  Children of channel objects, with __proto__ set
  56.  * to a CIRCUser object (automatically.)
  57.  *
  58.  * 1999-09-15 rginda@ndcico.com           v1.0
  59.  *
  60.  */
  61.  
  62. function userIsMe (user)
  63. {
  64.     
  65.     switch (user.TYPE)
  66.     {
  67.         case "IRCUser":
  68.             return (user == user.parent.me);
  69.             break;
  70.             
  71.         case "IRCChanUser":
  72.             return (user.__proto__ == user.parent.parent.me);
  73.             break;
  74.             
  75.         default:
  76.             return false;
  77.             
  78.     }
  79.  
  80.     return false;
  81. }
  82.  
  83. /*
  84.  * irc network
  85.  */
  86. function CIRCNetwork (name, serverList, eventPump)
  87. {
  88.  
  89.     this.name = name;
  90.     this.serverList = serverList;
  91.     this.eventPump = eventPump;
  92.     this.servers = new Object();
  93.  
  94. }
  95.  
  96. /** Clients should override this stuff themselves **/
  97. CIRCNetwork.prototype.INITIAL_NICK = "js-irc";
  98. CIRCNetwork.prototype.INITIAL_NAME = "INITIAL_NAME";
  99. CIRCNetwork.prototype.INITIAL_DESC = "INITIAL_DESC";
  100. CIRCNetwork.prototype.INITIAL_CHANNEL = "#jsbot"; 
  101. /* set INITIAL_CHANNEL to "" if you don't want a primary channel */
  102.  
  103. CIRCNetwork.prototype.MAX_CONNECT_ATTEMPTS = 20;
  104. CIRCNetwork.prototype.stayingPower = false; 
  105.  
  106. CIRCNetwork.prototype.TYPE = "IRCNetwork";
  107.  
  108. CIRCNetwork.prototype.connect =
  109. function net_conenct(pass)
  110. {
  111.  
  112.     this.connectAttempt = 0;
  113.     this.nextHost = 0;
  114.     var ev = new CEvent ("network", "do-connect", this, "onDoConnect");
  115.     ev.password = pass;
  116.     this.eventPump.addEvent (ev);
  117.  
  118. }
  119.  
  120. CIRCNetwork.prototype.quit =
  121. function net_quit (reason)
  122. {
  123.  
  124.     this.stayingPower = false;
  125.     if (this.isConnected())
  126.         this.primServ.logout (reason);
  127.  
  128. }
  129.  
  130. /*
  131.  * Handles a request to connect to a primary server.
  132.  */
  133. CIRCNetwork.prototype.onDoConnect =
  134. function net_doconnect(e)
  135. {
  136.     var c;
  137.     
  138.     if ((this.primServ) && (this.primServ.connection.isConnected))
  139.         return true;
  140.  
  141.     this.connecting = true; /* connection is considered "made" when server
  142.                              * sends a 001 message (see server.on001 */
  143.  
  144.     var ev;
  145.  
  146.     if (this.connectAttempt++ >= this.MAX_CONNECT_ATTEMPTS)
  147.     {
  148.         ev = new CEvent ("network", "info", this, "onInfo");
  149.         ev.msg = "Connection attempts exhausted, giving up.";
  150.         this.eventPump.addEvent (ev);        
  151.         return false;
  152.     }
  153.  
  154.     try
  155.     {
  156.         c = new CBSConnection();
  157.     }
  158.     catch (ex)
  159.     {
  160.         dd ("cant make socket.");
  161.         
  162.         ev = new CEvent ("network", "error", this, "onError");
  163.         ev.meat = "Unable to create socket: " + ex;
  164.         this.eventPump.addEvent (ev);
  165.         return false;
  166.     }
  167.  
  168.     host = this.nextHost++;
  169.     if (host >= this.serverList.length)
  170.     {
  171.         this.nextHost = 1;
  172.         host = 0;
  173.     }
  174.  
  175.     ev = new CEvent ("network", "info", this, "onInfo");
  176.     ev.msg = "Connecting to " + this.serverList[host].name + ":" +    
  177.         this.serverList[host].port + ", attempt " + this.connectAttempt +
  178.         " of " + this.MAX_CONNECT_ATTEMPTS + "...";
  179.     this.eventPump.addEvent (ev);
  180.  
  181.     var connected = false;
  182.     
  183.     if (c.connect (this.serverList[host].name, this.serverList[host].port,
  184.                    (void 0), true))
  185.     {
  186.         var ex;
  187.         ev = new CEvent ("network", "connect", this, "onConnect");
  188.         try
  189.         {
  190.             ev.server = this.primServ = new CIRCServer (this, c);
  191.             this.eventPump.addEvent (ev);
  192.             connected = true;
  193.         }
  194.         catch (ex)
  195.         {
  196.             dd ("Caught following exception creating new CIRCServer in " +
  197.                 "CIRCNetwork::onDoConnect().\n" + dumpObjectTree(ex));
  198.         }
  199.     }
  200.     
  201.  
  202.     if (!connected)
  203.     { /* connect failed, try again  */
  204.         ev = new CEvent ("network", "info", this, "onInfo");
  205.         ev.msg = "Couldn't connect to " + this.serverList[host].name + ":" +    
  206.             this.serverList[host].port + ", trying next server in list...";
  207.         this.eventPump.addEvent (ev);
  208.         
  209.         ev = new CEvent ("network", "do-connect", this, "onDoConnect");
  210.         this.eventPump.addEvent (ev);
  211.     }
  212.  
  213.     return true;
  214.  
  215. }
  216.  
  217. /*
  218.  * What to do when the client connects to it's primary server
  219.  */
  220. CIRCNetwork.prototype.onConnect = 
  221. function net_connect (e)
  222. {
  223.  
  224.     this.primServ = e.server;
  225.     this.primServ.login (this.INITIAL_NICK, this.INITIAL_NAME,
  226.                          this.INITIAL_DESC, e.pass);
  227.     return true;
  228.  
  229. }
  230.  
  231. CIRCNetwork.prototype.isConnected = 
  232. function net_connected (e)
  233. {
  234.     return (this.primServ && this.primServ.connection.isConnected);   
  235. }
  236.  
  237. /*
  238.  * irc server
  239.  */ 
  240. function CIRCServer (parent, connection)
  241. {
  242.     var serverName = connection.host + ":" + connection.port;
  243.     var s = parent.servers[serverName];
  244.     if (!s)
  245.     {
  246.         s = this;
  247.         s.channels = new Object();
  248.         s.users = new Object();
  249.     }
  250.     
  251.     s.parent = parent;
  252.     s.connection = connection;
  253.     s.sendQueue = new Array();
  254.     s.lastSend = new Date("1/1/1980");
  255.     s.sendsThisRound = 0;
  256.     s.savedLine = "";
  257.     s.lag = -1;    
  258.     s.usersStable = true;
  259.  
  260.     if (typeof connection.startAsyncRead == "function")
  261.         connection.startAsyncRead(s);
  262.     else
  263.         s.parent.eventPump.addEvent(new CEvent ("server", "poll", s,
  264.                                                 "onPoll"));
  265.  
  266.     parent.servers[serverName] = s;
  267.     return s;
  268.     
  269. }
  270.  
  271. CIRCServer.prototype.MAX_LINES_PER_SEND = 5;
  272. CIRCServer.prototype.MS_BETWEEN_SENDS = 1500;
  273. CIRCServer.prototype.READ_TIMEOUT = 100;
  274. CIRCServer.prototype.TOO_MANY_LINES_MSG = "\01ACTION has said too much\01";
  275. CIRCServer.prototype.VERSION_RPLY = "JS-IRC Library v0.01, " +
  276.     "Copyright (C) 1999 Robert Ginda; rginda@ndcico.com";
  277. CIRCServer.prototype.DEFAULT_REASON = "no reason";
  278.  
  279. CIRCServer.prototype.TYPE = "IRCServer";
  280.  
  281. CIRCServer.prototype.flushSendQueue =
  282. function serv_flush()
  283. {
  284.  
  285.     this.sendQueue.length = 0;
  286.     dd("sendQueue flushed.");
  287.  
  288.     return true;
  289.  
  290. }
  291.  
  292. CIRCServer.prototype.login =
  293. function serv_login(nick, name, desc, pass)
  294. {
  295.  
  296.     this.me = new CIRCUser (this, nick, name);
  297.     if (pass)
  298.        this.sendData ("PASS " + pass + "\n");
  299.     this.sendData ("NICK " + nick + "\n");
  300.     this.sendData ("USER " + name + " foo bar :" + desc + "\n");
  301.     
  302. }
  303.  
  304. CIRCServer.prototype.logout =
  305. function serv_logout(reason)
  306. {
  307.     
  308.     if (typeof reason == "undefined") reason = this.DEFAULT_REASON;
  309.  
  310.     this.connection.sendData ("QUIT :" + reason + "\n");
  311.     this.connection.disconnect();
  312.  
  313. }
  314.  
  315. CIRCServer.prototype.addChannel =
  316. function serv_addchan (name)
  317. {
  318.  
  319.     return new CIRCChannel (this, name);
  320.     
  321. }
  322.     
  323. CIRCServer.prototype.addUser =
  324. function serv_addusr (nick, name, host)
  325. {
  326.  
  327.     return new CIRCUser (this, nick, name, host);
  328.     
  329. }
  330.     
  331. CIRCServer.prototype.getChannelsLength =
  332. function serv_chanlen()
  333. {
  334.     var i = 0;
  335.  
  336.     for (var p in this.channels)
  337.         i++;
  338.  
  339.     return i;
  340.     
  341. }
  342.  
  343. CIRCServer.prototype.getUsersLength =
  344. function serv_chanlen()
  345. {
  346.     var i = 0;
  347.     
  348.     for (var p in this.users)
  349.         i++;
  350.  
  351.     return i;
  352.     
  353. }
  354.  
  355. CIRCServer.prototype.sendData =
  356. function serv_senddata (msg)
  357. {
  358.     
  359.     this.queuedSendData (msg);
  360.         
  361. }
  362.  
  363. CIRCServer.prototype.queuedSendData =
  364. function serv_senddata (msg)
  365. {
  366.  
  367.     if (this.sendQueue.length == 0)
  368.         this.parent.eventPump.addEvent (new CEvent ("server", "senddata",
  369.                                                     this, "onSendData"));
  370.     arrayInsertAt (this.sendQueue, 0, msg);
  371.         
  372. }
  373.  
  374. /*
  375.  * Takes care not to let more than MAX_LINES_PER_SEND lines out per
  376.  * cycle.  Cycle's are defined as the time between onPoll calls.
  377.  */
  378. CIRCServer.prototype.messageTo =
  379. function serv_messto (code, target, msg, ctcpCode)
  380. {
  381.     var lines = String(msg).split ("\n");
  382.     var sendable = 0, i;
  383.     var pfx = "", sfx = "";
  384.  
  385.     if (this.sendsThisRound > this.MAX_LINES_PER_SEND)
  386.         return false;
  387.  
  388.     if (ctcpCode)
  389.     {
  390.         pfx = "\01" + ctcpCode + " ";
  391.         sfx = "\01";
  392.     }
  393.  
  394.     for (i in lines)
  395.         if ((lines[i] != "") || ctcpCode) sendable++;
  396.  
  397.     for (i in lines)
  398.     {
  399.         if (((this.sendsThisRound == this.MAX_LINES_PER_SEND - 1) &&
  400.              (sendable > this.MAX_LINES_PER_SEND)) ||
  401.             (this.sendsThisRound == this.MAX_LINES_PER_SEND))
  402.         {
  403.             this.sendData ("PRIVMSG " + target + " :" +
  404.                            this.TOO_MANY_LINES_MSG + "\n");
  405.             this.sendsThisRound++;
  406.             return true;
  407.         }
  408.             
  409.         if ((lines[i] != "") || ctcpCode)
  410.         {
  411.             this.sendsThisRound++;
  412.             var line = code + " " + target + " :" + pfx + lines[i] + sfx;
  413.             //dd ("-*- irc sending '" +  line + "'");
  414.             this.sendData (line + "\n");
  415.         }
  416.         
  417.     }
  418.  
  419.     return true;        
  420. }
  421.  
  422. CIRCServer.prototype.sayTo = 
  423. function serv_sayto (target, msg)
  424. {
  425.  
  426.     this.messageTo ("PRIVMSG", target, msg);
  427.  
  428. }
  429.  
  430. CIRCServer.prototype.noticeTo = 
  431. function serv_noticeto (target, msg)
  432. {
  433.  
  434.     this.messageTo ("NOTICE", target, msg);
  435.  
  436. }
  437.  
  438. CIRCServer.prototype.actTo = 
  439. function serv_actto (target, msg)
  440. {
  441.  
  442.     this.messageTo ("PRIVMSG", target, msg, "ACTION");
  443.  
  444. }
  445.  
  446. CIRCServer.prototype.ctcpTo = 
  447. function serv_ctcpto (target, code, msg, method)
  448. {
  449.     if (typeof msg == "undefined")
  450.         msg = "";
  451.  
  452.     if (typeof method == "undefined")
  453.         method = "PRIVMSG";
  454.     
  455.      
  456.     this.messageTo (method, target, msg, code);
  457.  
  458. }
  459.  
  460. /**
  461.  * Abstracts the whois command.
  462.  *
  463.  * @param target        intended user(s).
  464.  */
  465. CIRCServer.prototype.whois = 
  466. function serv_whois (target) 
  467. {
  468.  
  469.     this.sendData ("WHOIS " + target + "\n");
  470.  
  471. }
  472.  
  473. CIRCServer.prototype.onDisconnect = 
  474. function serv_disconnect(e)
  475. {
  476.  
  477.     if ((this.parent.connecting) ||
  478.         /* fell off while connecting, try again */
  479.         (this.parent.primServ == this) && (this.parent.stayingPower))
  480.     { /* fell off primary server, reconnect to any host in the serverList */
  481.     var ev = new CEvent ("network", "do-connect", this.parent,
  482.                              "onDoConnect");
  483.     this.parent.eventPump.addEvent (ev);
  484.     }
  485.  
  486.     e.server = this;
  487.     e.set = "network";
  488.     e.destObject = this.parent;
  489.  
  490.     return true;
  491.  
  492. }
  493.  
  494. CIRCServer.prototype.onSendData =
  495. function serv_onsenddata (e)
  496. {
  497.     var d = new Date();
  498.  
  499.     if (!this.connection.isConnected)
  500.     {
  501.         var ev = new CEvent ("server", "disconnect", this,
  502.                              "onDisconnect");
  503.         ev.reason = "unknown";
  504.         this.parent.eventPump.addEvent (ev);
  505.         return false;
  506.     }
  507.  
  508.     this.sendsThisRound = 0;
  509.  
  510.     if (((d - this.lastSend) >= this.MS_BETWEEN_SENDS) &&
  511.         this.sendQueue.length > 0)                            
  512.     {
  513.         var s = this.sendQueue.pop();  
  514.       
  515.         if (s)
  516.         {
  517.             //dd ("queued send: " + s);
  518.             this.connection.sendData (s);
  519.             this.lastSend = d;
  520.         }
  521.  
  522.     }
  523.     else
  524.         this.parent.eventPump.addEvent (new CEvent ("event-pump", "yield",
  525.                                                     null, ""));
  526.  
  527.     if (this.sendQueue.length > 0)
  528.         this.parent.eventPump.addEvent (new CEvent ("server", "senddata",
  529.                                                     this, "onSendData"));
  530.     return true;
  531. }
  532.  
  533. CIRCServer.prototype.onPoll = 
  534. function serv_poll(e)
  535. {
  536.     var lines;
  537.     var ex;
  538.     var ev;
  539.     
  540.     try
  541.     {
  542.         line = this.connection.readData(this.READ_TIMEOUT);
  543.     }
  544.     catch (ex)
  545.     {
  546.         dd ("*** Caught exception " + ex + " reading from server " +
  547.             this.connection.host);
  548.         if (jsenv.HAS_RHINO && (ex instanceof java.lang.ThreadDeath))
  549.         {
  550.             dd("### catching a ThreadDeath");
  551.             throw(ex);
  552.         }
  553.         else if (typeof ex != "undefined")
  554.         {
  555.             ev = new CEvent ("server", "disconnect", this, "onDisconnect");
  556.             ev.reason = "error";
  557.             ev.exception = ex;
  558.             this.parent.eventPump.addEvent (ev);
  559.             return false;
  560.         }
  561.         else
  562.             line = ""
  563.     }
  564.         
  565.     this.parent.eventPump.addEvent (new CEvent ("server", "poll", this,
  566.                                                 "onPoll"));
  567.  
  568.     if (line)
  569.     {
  570.         ev = new CEvent ("server", "data-available", this, "onDataAvailable");
  571.         ev.line = line;
  572.         this.parent.eventPump.addEvent (ev);
  573.     }
  574.  
  575.     return true;
  576.     
  577. }
  578.  
  579. CIRCServer.prototype.onDataAvailable = 
  580. function serv_ppline(e)
  581. {
  582.     var line = e.line;
  583.     
  584.     if (line == "")
  585.         return false;
  586.     
  587.     var incomplete = (line[line.length] != '\n');
  588.     var lines = line.split("\n");
  589.  
  590.     if (this.savedLine)
  591.     {
  592.         lines[0] = this.savedLine + lines[0];
  593.         this.savedLine = "";
  594.     }
  595.     
  596.     if (incomplete)
  597.         this.savedLine = lines.pop();
  598.     
  599.     for (i in lines)
  600.     {
  601.         var ev = new CEvent("server", "rawdata", this, "onRawData");
  602.         ev.data = lines[i].replace(/\r/g, "");
  603.         this.parent.eventPump.addEvent (ev);
  604.     }
  605.  
  606.     return true;
  607. }
  608.  
  609. /*
  610.  * onRawData begins shaping the event by parsing the IRC message at it's
  611.  * simplest level.  After onRawData, the event will have the following
  612.  * properties:
  613.  * name           value
  614.  *
  615.  * set............"server"
  616.  * type..........."parsedata"
  617.  * destMethod....."onParsedData"
  618.  * destObject.....server (this)
  619.  * server.........server (this)
  620.  * connection.....CBSConnection (this.connection)
  621.  * source.........the <prefix> of the message (if it exists)
  622.  * user...........user object initialized with data from the message <prefix>
  623.  * params.........array containing the <middle> parameters of the message
  624.  * code...........the first <middle> parameter (most messages have this)
  625.  * meat...........the <trailing> parameter of the message
  626.  *
  627.  * See Section 2.3.1 of RFC 1459 for details on <prefix>, <middle> and
  628.  * <trailing> tokens.
  629.  */
  630. CIRCServer.prototype.onRawData = 
  631. function serv_onRawData(e)
  632. {
  633.     var ary;
  634.     var l = e.data;
  635.  
  636.     if (l[0] == ":")
  637.     {
  638.         ary = l.match (/:(\S+)\s(.*)/);
  639.         e.source = ary[1];
  640.         l = ary[2];
  641.         ary = e.source.match (/(\S+)!(\S+)@(.*)/);
  642.         if (ary)
  643.         {
  644.             e.user = new CIRCUser(this, ary[1], ary[2], ary[3]);
  645.         }
  646.         else
  647.         {
  648.             ary = e.source.match (/(\S+)@(.*)/);
  649.             if (ary)
  650.             {
  651.                 e.user = new CIRCUser(this, "", ary[1], ary[2]);
  652.             }
  653.         }
  654.     }
  655.  
  656.     e.server = this;
  657.  
  658.     var sep = l.indexOf(":");
  659.  
  660.     if (sep != -1) /* <trailing> param, if there is one */
  661.         e.meat = l.substr (sep + 1, l.length);
  662.     else
  663.         e.meat = "";
  664.  
  665.     if (sep != -1)
  666.         e.params = l.substr(0, sep).split(" ");
  667.     else
  668.         e.params = l.split(" ");
  669.     e.code = e.params[0].toUpperCase();
  670.     if (e.params[e.params.length - 1] == "")
  671.         e.params.length--;
  672.  
  673.     e.type = "parseddata";
  674.     e.destObject = this;
  675.     e.destMethod = "onParsedData";
  676.     
  677.     return true;
  678.     
  679. }
  680.  
  681. /*
  682.  * onParsedData forwards to next event, based on |e.code|
  683.  */
  684. CIRCServer.prototype.onParsedData = 
  685. function serv_onParsedData(e)
  686. {
  687.  
  688.     e.type = e.code.toLowerCase();
  689.     e.destMethod = "on" + e.code[0].toUpperCase() +
  690.         e.code.substr (1, e.code.length).toLowerCase();
  691.  
  692.     if (typeof this[e.destMethod] == "function")
  693.         e.destObject = this;
  694.     else if (typeof this["onUnknown"] == "function")
  695.         e.destMethod = "onUnknown";
  696.     else if (typeof this.parent[e.destMethod] == "function")
  697.     {
  698.         e.set = "network";
  699.         e.destObject = this.parent;
  700.     }
  701.     else
  702.     {
  703.         e.set = "network";
  704.         e.destObject = this.parent;
  705.         e.destMethod = "onUnknown";
  706.     }
  707.  
  708.     return true;
  709.     
  710. }
  711.  
  712. /* User changed topic */
  713. CIRCServer.prototype.onTopic = 
  714. function serv_topic (e)
  715. {
  716.  
  717.     e.channel = new CIRCChannel (this, e.params[1]);
  718.     e.channel.topicBy = e.user.nick;
  719.     e.channel.topicDate = new Date();
  720.     e.channel.topic = e.meat;
  721.     e.destObject = e.channel;
  722.     e.set = "channel";
  723.  
  724.     return true;
  725.     
  726. }
  727.  
  728. /* Successful login */
  729. CIRCServer.prototype.on001 =
  730. function serv_001 (e)
  731. {
  732.     this.parent.connectAttempt = 0;
  733.     this.parent.connecting = false;
  734.  
  735.     /* servers wont send a nick change notification if user was forced
  736.      * to change nick while logging in (eg. nick already in use.)  We need
  737.      * to verify here that what the server thinks our name is, matches what
  738.      * we think it is.  If not, the server wins.
  739.      */
  740.     if (e.params[1] != e.server.me.properNick)
  741.     {
  742.         renameProperty (e.server.users, e.server.me.nick, e.params[1]);
  743.         e.server.me.changeNick(e.params[1]);
  744.     }
  745.     
  746.     if (this.parent.INITIAL_CHANNEL)
  747.     {
  748.         this.parent.primChan = this.addChannel (this.parent.INITIAL_CHANNEL);
  749.         this.parent.primChan.join();
  750.     }
  751.  
  752.     e.destObject = this.parent;
  753.     e.set = "network";
  754. }
  755.  
  756.  
  757. /* TOPIC reply */
  758. CIRCServer.prototype.on332 =
  759. function serv_332 (e)
  760. {
  761.  
  762.     e.channel = new CIRCChannel (this, e.params[2]);
  763.     e.channel.topic = e.meat;
  764.     e.destObject = e.channel;
  765.     e.set = "channel";
  766.  
  767.     return true;
  768.     
  769. }
  770.  
  771. /* whois name */
  772. CIRCServer.prototype.on311 =
  773. function serv_311 (e)
  774. {
  775.     e.user = new CIRCUser (this, e.params[2], e.params[3], e.params[4]);
  776.     e.destObject = this.parent;
  777.     e.set = "network";
  778. }
  779.     
  780. /* whois server */
  781. CIRCServer.prototype.on312 =
  782. function serv_312 (e)
  783. {
  784.     e.user = new CIRCUser (this, e.params[2]);
  785.     e.user.connectionHost = e.params[3];
  786.  
  787.     e.destObject = this.parent;
  788.     e.set = "network";
  789. }
  790.  
  791. /* whois idle time */
  792. CIRCServer.prototype.on317 =
  793. function serv_317 (e)
  794. {
  795.     e.user = new CIRCUser (this, e.params[2]);
  796.     e.user.idleSeconds = e.params[3];
  797.  
  798.     e.destObject = this.parent;
  799.     e.set = "network";
  800. }
  801.  
  802. /* topic information */
  803. CIRCServer.prototype.on333 = 
  804. function serv_333 (e)
  805. {
  806.  
  807.     e.channel = new CIRCChannel (this, e.params[2]);
  808.     e.channel.topicBy = e.params[3];
  809.     e.channel.topicDate = new Date(Number(e.params[4]) * 1000);
  810.     e.destObject = e.channel;
  811.     e.set = "channel";
  812.  
  813.     return true;
  814.  
  815. }
  816.  
  817. /* name reply */
  818. CIRCServer.prototype.on353 = 
  819. function serv_353 (e)
  820. {
  821.     
  822.     e.channel = new CIRCChannel (this, e.params[3]);
  823.     if (e.channel.usersStable)
  824.     {        
  825.         e.channel.users = new Object();
  826.         e.channel.usersStable = false;
  827.     }
  828.     
  829.     e.destObject = e.channel;
  830.     e.set = "channel";
  831.  
  832.     var nicks = e.meat.split (" ");
  833.  
  834.     for (var n in nicks)
  835.     {
  836.         nick = nicks[n];
  837.         if (nick == "")
  838.             break;
  839.         
  840.         switch (nick[0])
  841.         {
  842.             case "@":
  843.                 e.user = new CIRCChanUser (e.channel,
  844.                                            nick.substr(1, nick.length),
  845.                                            true, (void 0));
  846.                 break;
  847.             
  848.             case "+":
  849.                 e.user = new CIRCChanUser (e.channel,
  850.                                            nick.substr(1, nick.length),
  851.                                            (void 0), true);
  852.                 break;
  853.             
  854.             default:
  855.                 e.user = new CIRCChanUser (e.channel, nick);
  856.                 break;
  857.         }
  858.  
  859.     }
  860.  
  861.     return true;
  862.     
  863. }
  864.  
  865. /* end of names */
  866. CIRCServer.prototype.on366 = 
  867. function serv_366 (e)
  868. {
  869.  
  870.     e.channel = new CIRCChannel (this, e.params[2]);
  871.     e.destObject = e.channel;
  872.     e.set = "channel";
  873.     e.channel.usersStable = true;
  874.  
  875.     return true;
  876.     
  877. }    
  878.  
  879. /* channel time stamp? */
  880. CIRCServer.prototype.on329 = 
  881. function serv_329 (e)
  882. {
  883.  
  884.     e.channel = new CIRCChannel (this, e.params[2]);
  885.     e.destObject = e.channel;
  886.     e.set = "channel";
  887.     e.channel.timeStamp = new Date (Number(e.params[3]) * 1000);
  888.     
  889.     return true;
  890.     
  891. }
  892.     
  893. /* channel mode reply */
  894. CIRCServer.prototype.on324 = 
  895. function serv_324 (e)
  896. {
  897.  
  898.     e.channel = new CIRCChannel (this, e.params[2]);
  899.     e.destObject = this;
  900.     e.type = "chanmode";
  901.     e.destMethod = "onChanMode";
  902.  
  903.     return true;
  904.  
  905. }
  906.  
  907. /* user changed the mode */
  908. CIRCServer.prototype.onMode = 
  909. function serv_mode (e)
  910. {
  911.  
  912.     e.destObject = this;
  913.     
  914.     if ((e.params[1][0] == "#") || (e.params[1][0] == "&"))
  915.     {
  916.         e.channel = new CIRCChannel (this, e.params[1]);
  917.         if (e.user)
  918.             e.user = new CIRCChanUser (e.channel, e.user.nick);
  919.         e.type = "chanmode";
  920.         e.destMethod = "onChanMode";
  921.     }
  922.     else
  923.     {
  924.         e.type = "usermode";
  925.         e.destMethod = "onUserMode";
  926.     }
  927.     
  928.     return true;
  929.     
  930. }
  931.  
  932. CIRCServer.prototype.onUserMode = 
  933. function serv_usermode (e)
  934. {
  935.  
  936.     return true;
  937.     
  938. }
  939.  
  940. CIRCServer.prototype.onChanMode = 
  941. function serv_chanmode (e)
  942. {
  943.     var modifier = "";
  944.     var params_eaten = 0;
  945.     var BASE_PARAM;
  946.  
  947.     if (e.code.toUpperCase() == "MODE")
  948.         BASE_PARAM = 2;
  949.     else
  950.         if (e.code == "324")
  951.             BASE_PARAM = 3;
  952.         else
  953.         {
  954.             dd ("** INVALID CODE in ChanMode event **");
  955.             return false;
  956.         }
  957.  
  958.     var mode_str = e.params[BASE_PARAM];
  959.     params_eaten++;
  960.  
  961.     e.modeStr = mode_str;
  962.     e.usersAffected = new Array();
  963.  
  964.     var nick;
  965.     var user;
  966.     
  967.     for (var i = 0; i < mode_str.length ; i++)
  968.     {
  969.         switch (mode_str[i])
  970.         {
  971.             case "+":
  972.             case "-":
  973.                 modifier = mode_str[i];
  974.                 break;
  975.  
  976.             /* user modes */
  977.             case "o": /* operator */
  978.                 if (modifier == "+")
  979.                 {
  980.                     nick = e.params[BASE_PARAM + params_eaten];
  981.                     user = new CIRCChanUser (e.channel, nick, true);
  982.                     params_eaten++;
  983.                     e.usersAffected.push (user);
  984.                 }
  985.                 else
  986.                     if (modifier == "-")
  987.                     {
  988.                         nick = e.params[BASE_PARAM + params_eaten];
  989.                         user = new CIRCChanUser (e.channel, nick, false);
  990.                         params_eaten++;
  991.                         e.usersAffected.push (user);
  992.                     }
  993.                 break;
  994.                         
  995.             case "v": /* voice */
  996.                 if (modifier == "+")
  997.                 {
  998.                     nick = e.params[BASE_PARAM + params_eaten];
  999.                     user = new CIRCChanUser (e.channel, nick, (void 0), true);
  1000.                     params_eaten++;
  1001.                     e.usersAffected.push (user);
  1002.                 }
  1003.                 else
  1004.                     if (modifier == "-")
  1005.                     {
  1006.                         nick = e.params[BASE_PARAM + params_eaten];
  1007.                         user = new CIRCChanUser (e.channel, nick, (void 0),
  1008.                                                  false);
  1009.                         params_eaten++;
  1010.                         e.usersAffected.push (user);
  1011.                     }
  1012.                 break;
  1013.                 
  1014.             case "b": /* ban */
  1015.                 var ban = e.params[BASE_PARAM + params_eaten];
  1016.                 params_eaten++;
  1017.  
  1018.                 if ((modifier == "+") &&
  1019.                     (typeof e.channel.bans[ban] == "undefined"))
  1020.                 {
  1021.                     e.channel.bans[ban] = {host: ban};
  1022.                     ban_evt = new CEvent ("channel", "ban", e.channel,
  1023.                                           "onBan");
  1024.                     ban_evt.channel = e.channel;
  1025.                     ban_evt.ban = ban;
  1026.                     ban_evt.source = e.user;
  1027.                     this.parent.eventPump.addEvent (e);
  1028.                 }
  1029.                 else
  1030.                     if (modifier == "-")
  1031.                         delete e.channel.bans[ban];
  1032.                 break;
  1033.  
  1034.                 
  1035.             /* channel modes */
  1036.             case "l": /* limit */
  1037.                 if (modifier == "+")
  1038.                 {
  1039.                     var limit = e.params[BASE_PARAM + params_eaten];
  1040.                     params_eaten++;
  1041.                     e.channel.mode.limit = limit;
  1042.                 }
  1043.                 else
  1044.                     if (modifier == "-")
  1045.                         e.channel.mode.limit = -1;
  1046.                 break;
  1047.  
  1048.             case "k": /* key */
  1049.                 var key = e.params[BASE_PARAM + params_eaten];
  1050.                 params_eaten++;
  1051.  
  1052.                 if (modifier == "+")
  1053.                     e.channel.mode.key = key;
  1054.                 else
  1055.                     if (modifier == "-")
  1056.                         e.channel.mode.key = "";
  1057.                 break;
  1058.  
  1059.             case "m": /* moderated */
  1060.                 if (modifier == "+")
  1061.                     e.channel.mode.moderated = true;
  1062.                 else
  1063.                     if (modifier == "-")
  1064.                         e.channel.mode.moderated = false;
  1065.                 break;
  1066.  
  1067.             case "n": /* no outside messages */
  1068.                 if (modifier == "+")
  1069.                     e.channel.mode.publicMessages = false;
  1070.                 else
  1071.                     if (modifier == "-")
  1072.                         e.channel.mode.publicMessages = true;
  1073.                 break;
  1074.  
  1075.             case "t": /* topic */
  1076.                 if (modifier == "+")
  1077.                     e.channel.mode.publicTopic = false;
  1078.                 else
  1079.                     if (modifier == "-")
  1080.                         e.channel.mode.publicTopic = true;
  1081.                 break;
  1082.                 
  1083.             case "i": /* invite */
  1084.                 if (modifier == "+")
  1085.                     e.channel.mode.invite = true;
  1086.                 else
  1087.                     if (modifier == "-")
  1088.                         e.channel.mode.invite = false;
  1089.                 break;
  1090.  
  1091.             case "s": /* secret */
  1092.                 if (modifier == "+")
  1093.                     e.channel.mode.secret  = true;
  1094.                 else
  1095.                     if (modifier == "-")
  1096.                         e.channel.mode.secret = false;
  1097.                 break;
  1098.                 
  1099.             case "p": /* private */
  1100.                 if (modifier == "+")
  1101.                     e.channel.mode.pvt = true;
  1102.                 else
  1103.                     if (modifier == "-")
  1104.                         e.channel.mode.pvt = false;
  1105.                 break;
  1106.                 
  1107.         }
  1108.     }
  1109.  
  1110.     e.destObject = e.channel;
  1111.     e.set = "channel";
  1112.     return true;
  1113.  
  1114. }
  1115.  
  1116. CIRCServer.prototype.onNick = 
  1117. function serv_nick (e)
  1118. {
  1119.     /* Some irc networks send the new nick in the meat, some send it in param[1]
  1120.      * Handle both cases. */
  1121.     var newNick = (e.meat) ? e.meat : e.params[1]; 
  1122.     var newKey = newNick.toLowerCase();
  1123.     var oldKey = e.user.nick;
  1124.     
  1125.     renameProperty (this.users, oldKey, newKey);
  1126.     e.oldNick = e.user.properNick;
  1127.     e.user.changeNick(newNick);
  1128.     
  1129.     for (var c in this.channels)
  1130.     {
  1131.         var cuser = this.channels[c].users[oldKey];
  1132.  
  1133.         if (typeof cuser != "undefined")
  1134.         {
  1135.             renameProperty (this.channels[c].users, oldKey, newKey);
  1136.             var ev = new CEvent ("channel", "nick", this.channels[c],
  1137.                                  "onNick");
  1138.             ev.channel = this.channels[c];
  1139.             ev.user = cuser;
  1140.             ev.server = this;
  1141.             ev.oldNick = e.oldNick;
  1142.             this.parent.eventPump.addEvent(ev);
  1143.         }
  1144.         
  1145.     }
  1146.  
  1147.     if (e.user == this.me)
  1148.     {
  1149.         /* if it was me, tell the network about the nick change as well */
  1150.         var ev = new CEvent ("network", "nick", this.parent, "onNick");
  1151.         ev.user = e.user;
  1152.         ev.server = this;
  1153.         ev.oldNick = e.oldNick;
  1154.         this.parent.eventPump.addEvent(ev);
  1155.     }
  1156.  
  1157.     e.destObject = e.user;
  1158.     e.set = "user";    
  1159.  
  1160.     return true;
  1161.     
  1162. }
  1163.  
  1164. CIRCServer.prototype.onQuit = 
  1165. function serv_quit (e)
  1166. {
  1167.  
  1168.     for (var c in e.server.channels)
  1169.     {
  1170.         if (e.server.channels[c].users[e.user.nick])
  1171.         {
  1172.             var ev = new CEvent ("channel", "quit", e.server.channels[c],
  1173.                                  "onQuit");
  1174.             ev.user = e.server.channels[c].users[e.user.nick];
  1175.             ev.channel = e.server.channels[c];
  1176.             ev.server = ev.channel.parent;
  1177.             ev.reason = e.meat;
  1178.             this.parent.eventPump.addEvent(ev);
  1179.             delete e.server.channels[c].users[e.user.nick];
  1180.         }
  1181.     }
  1182.  
  1183.     this.users[e.user.nick].lastQuitMessage = e.meat;
  1184.     this.users[e.user.nick].lastQuitDate = new Date;
  1185.  
  1186.     e.reason = e.meat;
  1187.     e.destObject = e.user;
  1188.     e.set = "user";
  1189.  
  1190.     return true;
  1191.  
  1192. }
  1193.  
  1194. CIRCServer.prototype.onPart = 
  1195. function serv_part (e)
  1196. {
  1197.  
  1198.     e.channel = new CIRCChannel (this, e.params[1]);    
  1199.     e.user = new CIRCChanUser (e.channel, e.user.nick);
  1200.     e.channel.removeUser(e.user.nick);
  1201.     e.destObject = e.channel;
  1202.     e.set = "channel";
  1203.  
  1204.     return true;
  1205.     
  1206. }
  1207.  
  1208. CIRCServer.prototype.onKick = 
  1209. function serv_kick (e)
  1210. {
  1211.  
  1212.     e.channel = new CIRCChannel (this, e.params[1]);
  1213.     e.lamer = new CIRCChanUser (e.channel, e.params[2]);
  1214.     delete e.channel.users[e.lamer.nick];
  1215.     e.reason = e.meat;
  1216.     e.destObject = e.channel;
  1217.     e.set = "channel"; 
  1218.  
  1219.     return true;
  1220.     
  1221. }
  1222.  
  1223. CIRCServer.prototype.onJoin = 
  1224. function serv_join (e)
  1225. {
  1226.  
  1227.     e.channel = new CIRCChannel (this, e.meat);
  1228.     if (e.user == this.me)
  1229.         e.server.sendData ("MODE " + e.channel.name + "\n" /* +
  1230.                            "BANS " + e.channel.name + "\n" */);
  1231.     e.user = new CIRCChanUser (e.channel, e.user.nick);
  1232.  
  1233.     e.destObject = e.channel;
  1234.     e.set = "channel";
  1235.  
  1236.     return true;
  1237.     
  1238. }
  1239.  
  1240. CIRCServer.prototype.onPing = 
  1241. function serv_ping (e)
  1242. {
  1243.  
  1244.     /* non-queued send, so we can calcualte lag */
  1245.     this.connection.sendData ("PONG :" + e.meat + "\n");
  1246.     this.connection.sendData ("PING :" + e.meat + "\n");
  1247.     this.lastPing = this.lastPingSent = new Date();
  1248.  
  1249.     e.destObject = this.parent;
  1250.     e.set = "network";
  1251.  
  1252.     return true;
  1253.     
  1254. }
  1255.  
  1256. CIRCServer.prototype.onPong = 
  1257. function serv_pong (e)
  1258. {
  1259.  
  1260.     if (this.lastPingSent)
  1261.         this.lag = roundTo ((new Date() - this.lastPingSent) / 1000, 1);
  1262.  
  1263.     delete this.lastPingSent;
  1264.     
  1265.     e.destObject = this.parent;
  1266.     e.set = "network";
  1267.  
  1268.     return true;
  1269.     
  1270. }
  1271.  
  1272. CIRCServer.prototype.onNotice = 
  1273. function serv_notice (e)
  1274. {
  1275.  
  1276.     if (!e.user)
  1277.     {
  1278.         e.set = "network";
  1279.         e.destObject = this.parent;
  1280.         return true;
  1281.     }
  1282.         
  1283.     if ((e.params[1][0] == "#") || (e.params[1][0] == "&"))
  1284.     {
  1285.         e.channel = new CIRCChannel(this, e.params[1]);
  1286.         e.user = new CIRCChanUser (e.channel, e.user.nick);
  1287.         e.replyTo = e.channel;
  1288.         e.set = "channel";
  1289.     }
  1290.     else
  1291.     {
  1292.         e.set = "user";
  1293.         e.replyTo = e.user; /* send replys to the user who sent the message */
  1294.     }
  1295.  
  1296.     e.destObject = e.replyTo;
  1297.  
  1298.     return true;
  1299.     
  1300. }
  1301.  
  1302. CIRCServer.prototype.onPrivmsg = 
  1303. function serv_privmsg (e)
  1304. {
  1305.     
  1306.     /* setting replyTo provides a standard place to find the target for     */
  1307.     /* replys associated with this event.                                   */
  1308.     if ((e.params[1][0] == "#") || (e.params[1][0] == "&"))
  1309.     {
  1310.         e.channel = new CIRCChannel(this, e.params[1]);
  1311.         e.user = new CIRCChanUser (e.channel, e.user.nick);
  1312.         e.replyTo = e.channel;
  1313.         e.set = "channel";
  1314.     }
  1315.     else
  1316.     {
  1317.         e.set = "user";
  1318.         e.replyTo = e.user; /* send replys to the user who sent the message */
  1319.     }
  1320.  
  1321.     if (e.meat.search (/\x01.*\x01/i) != -1)
  1322.     {
  1323.         e.type = "ctcp";
  1324.         e.destMethod = "onCTCP";
  1325.         e.set = "server";
  1326.         e.destObject = this;
  1327.     }
  1328.     else
  1329.         e.destObject = e.replyTo;
  1330.  
  1331.     return true;
  1332.     
  1333. }
  1334.  
  1335. CIRCServer.prototype.onCTCP = 
  1336. function serv_ctcp (e)
  1337. {
  1338.     var ary = e.meat.match (/^\x01(\S+) ?(.*)\x01$/i);
  1339.  
  1340.     if (ary == null)
  1341.         return false;
  1342.  
  1343.     e.CTCPData = ary[2] ? ary[2] : "";
  1344.  
  1345.     e.CTCPCode = ary[1].toLowerCase();
  1346.     e.type = "ctcp-" + e.CTCPCode;
  1347.     e.destMethod = "onCTCP" + ary[1][0].toUpperCase() +
  1348.         ary[1].substr (1, ary[1].length).toLowerCase();
  1349.  
  1350.     if (typeof this[e.destMethod] != "function")
  1351.     { /* if there's no place to land the event here, try to forward it */
  1352.         e.destObject = e.replyTo;
  1353.         e.set = (e.replyTo == e.user) ? "user" : "channel";
  1354.         
  1355.         if (typeof e.replyTo[e.destMethod] != "function")
  1356.         { /* if there's no place to forward it, send it to unknownCTCP */
  1357.             e.type = "unk-ctcp";
  1358.             e.destMethod = "onUnknownCTCP";
  1359.         }
  1360.     }
  1361.     else
  1362.         e.destObject = this;
  1363.  
  1364.     return true;
  1365.     
  1366. }
  1367.  
  1368. CIRCServer.prototype.onCTCPAction =
  1369. function serv_cact (e)
  1370. {
  1371.     e.destObject = e.replyTo;
  1372.     e.set = (e.replyTo == e.user) ? "user" : "channel";        
  1373.  
  1374. }
  1375.  
  1376.  
  1377. CIRCServer.prototype.onCTCPVersion = 
  1378. function serv_cver (e)
  1379. {
  1380.     var lines = e.server.VERSION_RPLY.split ("\n");
  1381.     
  1382.     for (var i in lines)
  1383.         e.user.notice ("\01VERSION " + lines[i] + "\01");
  1384.     
  1385.     e.destObject = e.replyTo;
  1386.     e.set = (e.replyTo == e.user) ? "user" : "channel";
  1387.  
  1388.     return true;
  1389.     
  1390. }
  1391.  
  1392. CIRCServer.prototype.onCTCPPing = 
  1393. function serv_cping (e)
  1394. {
  1395.  
  1396.     /* non-queued send */
  1397.     e.server.connection.sendData ("NOTICE " + e.user.nick + " :\01PING " +
  1398.                                   e.CTCPData + "\01\n");
  1399.     e.destObject = e.replyTo;
  1400.     e.set = (e.replyTo == e.user) ? "user" : "channel";
  1401.  
  1402.     return true;
  1403.     
  1404. }
  1405.  
  1406. CIRCServer.prototype.onCTCPDcc = 
  1407. function serv_dcc (e)
  1408. {
  1409.  
  1410.     var ary = e.CTCPData.match (/(\S+)? ?(.*)/);
  1411.     
  1412.     e.DCCData = ary[2];
  1413.     e.type = "dcc-" + ary[1].toLowerCase();
  1414.     e.destMethod = "onDCC" + ary[1][0].toUpperCase() +
  1415.         ary[1].substr (1, ary[1].length).toLowerCase();
  1416.  
  1417.     if (typeof this[e.destMethod] != "function")
  1418.     { /* if there's no place to land the event here, try to forward it */
  1419.         e.destObject = e.replyTo;
  1420.         e.set = (e.replyTo == e.user) ? "user" : "channel";
  1421.     }
  1422.     else
  1423.         e.destObject = this;
  1424.  
  1425.     return true;
  1426.  
  1427. }
  1428.  
  1429. CIRCServer.prototype.onDCCChat = 
  1430. function serv_dccchat (e)
  1431. {
  1432.     var ary = e.DCCData.match (/(chat) (\d+) (\d+)/i);
  1433.  
  1434.     if (ary == null)
  1435.         return false;
  1436.  
  1437.     e.id = ary[2];
  1438.     e.port = ary[3];
  1439.     e.destObject = e.replyTo;
  1440.     e.set = (e.replyTo == e.user) ? "user" : "channel";
  1441.     
  1442.     return true;
  1443.     
  1444. }
  1445.  
  1446. CIRCServer.prototype.onDCCSend = 
  1447. function serv_dccsend (e)
  1448. {
  1449.  
  1450.     var ary = e.DCCData.match (/(\S+) (\d+) (\d+) (\d+)/);
  1451.  
  1452.     if (ary == null)
  1453.         return false;
  1454.     
  1455.     e.file = ary[1];
  1456.     e.id   = ary[2];
  1457.     e.port = ary[3];
  1458.     e.size = ary[4];
  1459.     e.destObject = e.replyTo;
  1460.     e.set = (e.replyTo == e.user) ? "user" : "channel";
  1461.  
  1462.     return true;
  1463.     
  1464. }
  1465.  
  1466. /*
  1467.  * channel
  1468.  */
  1469.  
  1470. function CIRCChannel (parent, name)
  1471. {
  1472.  
  1473.     name = name.toLowerCase();
  1474.     
  1475.     var existingChannel = parent.channels[name];
  1476.  
  1477.     if (typeof existingChannel == "object")
  1478.         return existingChannel;
  1479.     
  1480.     this.parent = parent;
  1481.     this.name = name;
  1482.     this.users = new Object();
  1483.     this.bans = new Object();
  1484.     this.mode = new CIRCChanMode (this);
  1485.     
  1486.     parent.channels[name] = this;
  1487.  
  1488.     return this;
  1489.     
  1490. }
  1491.  
  1492. CIRCChannel.prototype.TYPE = "IRCChannel";
  1493.     
  1494. CIRCChannel.prototype.addUser = 
  1495. function chan_adduser (nick, isOp, isVoice)
  1496. {
  1497.  
  1498.     return new CIRCChanUser (this, nick, isOp, isVoice);
  1499.     
  1500. }
  1501.  
  1502. CIRCChannel.prototype.getUser =
  1503. function chan_getuser (nick) 
  1504. {
  1505.     
  1506.     nick = nick.toLowerCase(); // assumes valid param!
  1507.     var cuser = this.users[nick];
  1508.     return cuser; // caller expected to check for undefinededness    
  1509.  
  1510. }
  1511.  
  1512. CIRCChannel.prototype.removeUser =
  1513. function chan_removeuser (nick)
  1514. {
  1515.     delete this.users[nick.toLowerCase()]; // see ya
  1516. }
  1517.  
  1518. CIRCChannel.prototype.getUsersLength = 
  1519. function chan_userslen ()
  1520. {
  1521.     var i = 0;
  1522.     this.opCount = 0;
  1523.     this.voiceCount = 0;
  1524.     
  1525.     for (var p in this.users)
  1526.     {
  1527.         if (this.users[p].isOp)
  1528.             ++this.opCount;
  1529.         else if (this.users[p].isVoice)
  1530.             ++this.voiceCount;
  1531.         i++;
  1532.     }
  1533.  
  1534.     return i;
  1535.     
  1536. }
  1537.  
  1538. CIRCChannel.prototype.setTopic = 
  1539. function chan_topic (str)
  1540. {
  1541.     if ((!this.mode.publicTopic) && 
  1542.         (!this.parent.me.isOp))
  1543.         return false;
  1544.     
  1545.     str = String(str).split("\n");
  1546.     for (var i in str)
  1547.         this.parent.sendData ("TOPIC " + this.name + " :" + str[i] + "\n");
  1548.     
  1549.     return true;
  1550.  
  1551. }
  1552.  
  1553. CIRCChannel.prototype.say = 
  1554. function chan_say (msg)
  1555. {
  1556.  
  1557.     this.parent.sayTo (this.name, msg);
  1558.     
  1559. }
  1560.  
  1561. CIRCChannel.prototype.act = 
  1562. function chan_say (msg)
  1563. {
  1564.  
  1565.     this.parent.actTo (this.name, msg);
  1566.     
  1567. }
  1568.  
  1569. CIRCChannel.prototype.notice = 
  1570. function chan_notice (msg)
  1571. {
  1572.  
  1573.     this.parent.noticeTo (this.name, msg);
  1574.     
  1575. }
  1576.  
  1577. CIRCChannel.prototype.ctcp = 
  1578. function chan_ctcpto (code, msg, type)
  1579. {
  1580.     if (typeof msg == "undefined")
  1581.         msg = "";
  1582.  
  1583.     if (typeof type == "undefined")
  1584.         type = "PRIVMSG";
  1585.     
  1586.      
  1587.     this.parent.messageTo (type, this.name, msg, code);
  1588.  
  1589. }
  1590.  
  1591. CIRCChannel.prototype.join = 
  1592. function chan_join (key)
  1593. {
  1594.     if (!key)
  1595.         key = "";
  1596.     
  1597.     this.parent.sendData ("JOIN " + this.name + " " + key + "\n");
  1598.     return true;
  1599.     
  1600. }
  1601.  
  1602. CIRCChannel.prototype.part = 
  1603. function chan_part ()
  1604. {
  1605.     
  1606.     this.parent.sendData ("PART " + this.name + "\n");
  1607.     this.users = new Object();
  1608.     return true;
  1609.     
  1610. }
  1611.  
  1612. /**
  1613.  * Invites a user to a channel.
  1614.  *
  1615.  * @param nick  the user name to invite.
  1616.  */
  1617. CIRCChannel.prototype.invite =
  1618. function chan_inviteuser (nick)
  1619. {
  1620.  
  1621.     this.parent.sendData("INVITE " + nick + " " + this.name + "\n");
  1622.     return true;
  1623.  
  1624. }
  1625.  
  1626. /*
  1627.  * channel mode
  1628.  */
  1629. function CIRCChanMode (parent)
  1630. {
  1631.  
  1632.     this.parent = parent;
  1633.     this.limit = -1;
  1634.     this.key = "";
  1635.     this.moderated = false;
  1636.     this.publicMessages = true;
  1637.     this.publicTopic = true;
  1638.     this.invite = false;
  1639.     this.pvt = false;
  1640.     
  1641. }
  1642.  
  1643. CIRCChanMode.prototype.TYPE = "IRCChanMode";
  1644.  
  1645. CIRCChanMode.prototype.getModeStr = 
  1646. function chan_modestr (f)
  1647. {
  1648.     var str = "";
  1649.  
  1650.     if (this.invite)
  1651.         str += "i";
  1652.     if (this.moderated)
  1653.         str += "m";
  1654.     if (!this.publicMessages)
  1655.         str += "n";
  1656.     if (!this.publicTopic)
  1657.         str += "t";
  1658.     if (this.pvt)
  1659.         str += "p";
  1660.  
  1661.     if (str)
  1662.         str = "+" + str;
  1663.  
  1664.     return str;
  1665.     
  1666. }
  1667.  
  1668. CIRCChanMode.prototype.setMode = 
  1669. function chanm_mode (modestr)
  1670. {
  1671.     if (!this.parent.users[this.parent.parent.me.nick].isOp)
  1672.         return false;
  1673.  
  1674.     this.parent.parent.sendData ("MODE " + this.parent.name + " " +
  1675.                                  modestr + "\n");
  1676.  
  1677.     return true;
  1678.     
  1679. }
  1680.  
  1681. CIRCChanMode.prototype.setLimit = 
  1682. function chanm_limit (n)
  1683. {
  1684.     if (!this.parent.users[this.parent.parent.me.nick].isOp)
  1685.         return false;
  1686.  
  1687.     if ((typeof n == "undefined") || (n <= 0))
  1688.         this.parent.parent.sendData ("MODE " + this.parent.name + " -l\n");
  1689.     else
  1690.         this.parent.parent.sendData ("MODE " + this.parent.name + " +l " +
  1691.                                      Number(n) + "\n");
  1692.  
  1693.     return true;
  1694.     
  1695. }
  1696.  
  1697. CIRCChanMode.prototype.lock = 
  1698. function chanm_lock (k)
  1699. {
  1700.     if (!this.parent.users[this.parent.parent.me.nick].isOp)
  1701.         return false;
  1702.     
  1703.     this.parent.parent.sendData ("MODE " + this.parent.name + " +k " +
  1704.                                  k + "\n");
  1705.     return true;
  1706.     
  1707. }
  1708.  
  1709. CIRCChanMode.prototype.unlock = 
  1710. function chan_unlock (k)
  1711. {
  1712.     if (!this.parent.users[this.parent.parent.me.nick].isOp)
  1713.         return false;
  1714.     
  1715.     this.parent.parent.sendData ("MODE " + this.parent.name + " -k " +
  1716.                                  k + "\n");
  1717.     return true;
  1718.     
  1719. }
  1720.  
  1721. CIRCChanMode.prototype.setModerated = 
  1722. function chan_moderate (f)
  1723. {
  1724.     if (!this.parent.users[this.parent.parent.me.nick].isOp)
  1725.         return false;
  1726.  
  1727.     var modifier = (f) ? "+" : "-";
  1728.     
  1729.     this.parent.parent.sendData ("MODE " + this.parent.name + " " +
  1730.                                  modifier + "m\n");
  1731.     return true;
  1732.     
  1733. }
  1734.  
  1735. CIRCChanMode.prototype.setPublicMessages = 
  1736. function chan_pmessages (f)
  1737. {
  1738.     if (!this.parent.users[this.parent.parent.me.nick].isOp)
  1739.         return false;
  1740.  
  1741.     var modifier = (f) ? "-" : "+";
  1742.     
  1743.     this.parent.parent.sendData ("MODE " + this.parent.name + " " +
  1744.                                  modifier + "n\n");
  1745.     return true;
  1746.     
  1747. }
  1748.  
  1749. CIRCChanMode.prototype.setPublicTopic = 
  1750. function chan_ptopic (f)
  1751. {
  1752.     if (!this.parent.users[this.parent.parent.me.nick].isOp)
  1753.         return false;
  1754.  
  1755.     var modifier = (f) ? "-" : "+";
  1756.     
  1757.     this.parent.parent.sendData ("MODE " + this.parent.name + " " +
  1758.                                  modifier + "t\n");
  1759.     return true;
  1760.     
  1761. }
  1762.  
  1763. CIRCChanMode.prototype.setInvite = 
  1764. function chan_invite (f)
  1765. {
  1766.     if (!this.parent.users[this.parent.parent.me.nick].isOp)
  1767.         return false;
  1768.  
  1769.     var modifier = (f) ? "+" : "-";
  1770.     
  1771.     this.parent.parent.sendData ("MODE " + this.parent.name + " " +
  1772.                                  modifier + "i\n");
  1773.     return true;
  1774.     
  1775. }
  1776.  
  1777. CIRCChanMode.prototype.setPvt = 
  1778. function chan_pvt (f)
  1779. {
  1780.     if (!this.parent.users[this.parent.parent.me.nick].isOp)
  1781.         return false;
  1782.  
  1783.     var modifier = (f) ? "+" : "-";
  1784.     
  1785.     this.parent.parent.sendData ("MODE " + this.parent.name + " " +
  1786.                                  modifier + "p\n");
  1787.     return true;
  1788.     
  1789. }
  1790.  
  1791. CIRCChanMode.prototype.setSecret = 
  1792. function chan_secret (f)
  1793. {
  1794.     if (!this.parent.users[this.parent.parent.me.nick].isOp)
  1795.         return false;
  1796.  
  1797.     var modifier = (f) ? "+" : "-";
  1798.     
  1799.     this.parent.parent.sendData ("MODE " + this.parent.name + " " +
  1800.                                  modifier + "p\n");
  1801.     return true;
  1802.     
  1803. }
  1804.  
  1805. /*
  1806.  * user
  1807.  */
  1808.  
  1809. function CIRCUser (parent, nick, name, host)
  1810. {
  1811.     var properNick = nick;
  1812.     nick = nick.toLowerCase();
  1813.     
  1814.     var existingUser = parent.users[nick];
  1815.  
  1816.     if (typeof existingUser == "object")
  1817.     {
  1818.         if (name) existingUser.name = name;
  1819.         if (host) existingUser.host = host;
  1820.         return existingUser;
  1821.     }
  1822.  
  1823.     this.parent = parent;
  1824.     this.nick = nick;
  1825.     this.properNick = properNick;
  1826.     this.name = name;
  1827.     this.host = host;
  1828.  
  1829.     parent.users[nick] = this;
  1830.  
  1831.     return this;
  1832.  
  1833. }
  1834.  
  1835. CIRCUser.prototype.TYPE = "IRCUser";
  1836.  
  1837. CIRCUser.prototype.changeNick =
  1838. function usr_changenick (nick)
  1839. {
  1840.  
  1841.     this.properNick = nick;
  1842.     this.nick = nick.toLowerCase();
  1843.     
  1844. }
  1845.  
  1846. CIRCUser.prototype.getHostMask = 
  1847. function usr_hostmask (pfx)
  1848. {
  1849.     pfx = (typeof pfx != "undefined") ? pfx : "*!" + this.name + "@*.";
  1850.     var idx = this.host.indexOf(".");
  1851.     if (idx == -1)
  1852.         return pfx + this.host;
  1853.     
  1854.     return (pfx + this.host.substr(idx + 1, this.host.length));
  1855.     
  1856. }
  1857.  
  1858. CIRCUser.prototype.say = 
  1859. function usr_say (msg)
  1860. {
  1861.  
  1862.     this.parent.sayTo (this.nick, msg);
  1863.     
  1864. }
  1865.  
  1866. CIRCUser.prototype.notice = 
  1867. function usr_notice (msg)
  1868. {
  1869.  
  1870.     this.parent.noticeTo (this.nick, msg);
  1871.     
  1872. }
  1873.  
  1874. CIRCUser.prototype.act = 
  1875. function usr_act (msg)
  1876. {
  1877.  
  1878.     this.parent.actTo (this.nick, msg);
  1879.     
  1880. }
  1881.  
  1882. CIRCUser.prototype.ctcp = 
  1883. function usr_ctcp (code, msg, type)
  1884. {
  1885.     if (typeof msg == "undefined")
  1886.         msg = "";
  1887.  
  1888.     if (typeof type == "undefined")
  1889.         type = "PRIVMSG";
  1890.     
  1891.      
  1892.     this.parent.messageTo (type, this.name, msg, code);
  1893.  
  1894. }
  1895.  
  1896. CIRCUser.prototype.whois =
  1897. function usr_whois ()
  1898. {
  1899.  
  1900.     this.parent.whois (this.nick);
  1901. }   
  1902.  
  1903.     
  1904. /*
  1905.  * channel user
  1906.  */
  1907. function CIRCChanUser (parent, nick, isOp, isVoice)
  1908. {
  1909.     var properNick = nick;
  1910.     nick = nick.toLowerCase();    
  1911.  
  1912.     var existingUser = parent.users[nick];
  1913.  
  1914.     if (typeof existingUser != "undefined")
  1915.     {
  1916.         if (typeof isOp != "undefined") existingUser.isOp = isOp;
  1917.         if (typeof isVoice != "undefined") existingUser.isVoice = isVoice;
  1918.         return existingUser;
  1919.     }
  1920.         
  1921.     protoUser = new CIRCUser (parent.parent, properNick);
  1922.         
  1923.     this.__proto__ = protoUser;
  1924.     this.setOp = cusr_setop;
  1925.     this.setVoice = cusr_setvoice;
  1926.     this.setBan = cusr_setban;
  1927.     this.kick = cusr_kick;
  1928.     this.kickBan = cusr_kban;
  1929.     this.say = cusr_say;
  1930.     this.notice = cusr_notice;
  1931.     this.act = cusr_act;
  1932.     this.whois = cusr_whois;
  1933.     this.parent = parent;
  1934.     this.isOp = (typeof isOp != "undefined") ? isOp : false;
  1935.     this.isVoice = (typeof isVoice != "undefined") ? isVoice : false;
  1936.     this.TYPE = "IRCChanUser";
  1937.     
  1938.     parent.users[nick] = this;
  1939.  
  1940.     return this;
  1941. }
  1942.  
  1943. function cusr_setop (f)
  1944. {
  1945.     var server = this.parent.parent;
  1946.     var me = server.me;
  1947.  
  1948.     if (!this.parent.users[me.nick].isOp)
  1949.         return false;
  1950.  
  1951.     var modifier = (f) ? " +o " : " -o ";
  1952.     server.sendData("MODE " + this.parent.name + modifier + this.nick + "\n");
  1953.  
  1954.     return true;
  1955.     
  1956. }
  1957.  
  1958. function cusr_setvoice (f)
  1959. {
  1960.     var server = this.parent.parent;
  1961.     var me = server.me;
  1962.  
  1963.     if (!this.parent.users[me.nick].isOp)
  1964.         return false;
  1965.     
  1966.     var modifier = (f) ? " +v " : " -v ";
  1967.     server.sendData("MODE " + this.parent.name + modifier + this.nick + "\n");
  1968.  
  1969.     return true;
  1970.     
  1971. }
  1972.  
  1973. function cusr_kick (reason)
  1974. {
  1975.     var server = this.parent.parent;
  1976.     var me = server.me;
  1977.  
  1978.     reason = (typeof reason != "undefined") ? reason : this.nick;
  1979.     if (!this.parent.users[me.nick].isOp)
  1980.         return false;
  1981.     
  1982.     server.sendData("KICK " + this.parent.name + " " + this.nick + " :" +
  1983.                     reason + "\n");
  1984.  
  1985.     return true;
  1986.     
  1987. }
  1988.  
  1989. function cusr_setban (f)
  1990. {
  1991.     var server = this.parent.parent;
  1992.     var me = server.me;
  1993.  
  1994.     if (!this.parent.users[me.nick].isOp)
  1995.         return false;
  1996.  
  1997.     if (!this.host)
  1998.         return false;
  1999.  
  2000.     var modifier = (f) ? " +b " : " -b ";
  2001.     modifier += this.getHostMask() + " ";
  2002.     
  2003.     server.sendData("MODE " + this.parent.name + modifier + "\n");
  2004.  
  2005.     return true;
  2006.     
  2007. }
  2008.  
  2009. function cusr_kban (reason)
  2010. {
  2011.     var server = this.parent.parent;
  2012.     var me = server.me;
  2013.  
  2014.     if (!this.parent.users[me.nick].isOp)
  2015.         return false;
  2016.  
  2017.     if (!this.host)
  2018.         return false;
  2019.  
  2020.     reason = (typeof reason != "undefined") ? reason : this.nick;
  2021.     var modifier = " -o+b " + this.nick + " " + this.getHostMask() + " ";
  2022.     
  2023.     server.sendData("MODE " + this.parent.name + modifier + "\n" +
  2024.                     "KICK " + this.parent.name + " " + this.nick + " :" +
  2025.                     reason + "\n");
  2026.  
  2027.     return true;
  2028.     
  2029. }
  2030.  
  2031. function cusr_say (msg)
  2032. {
  2033.  
  2034.     this.__proto__.say (msg);
  2035.  
  2036. }
  2037.  
  2038. function cusr_notice (msg)
  2039. {
  2040.  
  2041.     this.__proto__.notice (msg);
  2042.     
  2043. }
  2044.  
  2045. function cusr_act (msg)
  2046. {
  2047.  
  2048.     this.__proto__.act (msg);
  2049.     
  2050. }
  2051.  
  2052. function cusr_whois ()
  2053. {
  2054.  
  2055.     this.__proto__.whois ();
  2056.  
  2057. }
  2058.