Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
  • dailybuild_linux-x64
  • dailybuild_win32
  • sqlite
  • rip_abstraction
  • dailybuild_macos-armv8
  • dd_file_lister_filanem_in_desc_color
  • mode7
  • dd_msg_reader_are_you_there_warning_improvement
  • c23-playing
  • syncterm-1.3
  • syncterm-1.2
  • test-build
  • hide_remote_connection_with_telgate
  • 638-can-t-control-c-during-a-file-search
  • add_body_to_pager_email
  • mingw32-build
  • cryptlib-3.4.7
  • ree/mastermind
  • new_user_dat
  • sbbs320d
  • syncterm-1.6
  • syncterm-1.5
  • syncterm-1.4
  • sbbs320b
  • syncterm-1.3
  • syncterm-1.2
  • syncterm-1.2rc6
  • syncterm-1.2rc5
  • push
  • syncterm-1.2rc4
  • syncterm-1.2rc2
  • syncterm-1.2rc1
  • sbbs319b
  • sbbs318b
  • goodbuild_linux-x64_Sep-01-2020
  • goodbuild_win32_Sep-01-2020
  • goodbuild_linux-x64_Aug-31-2020
  • goodbuild_win32_Aug-31-2020
  • goodbuild_win32_Aug-30-2020
40 results

irc.js

Blame
    • cyan's avatar
      3dc1d985
      * irc.js is now 'themeable' through -t, see irc-default.js, and irc-cyan.js · 3dc1d985
      cyan authored
        for examples on how to change the look of irc.js
      * Prefix on join/part/kick, etc no long shows the nick! part of the n!u@h
      * Tab completion bug fixed. (hitting tab wouldn't force cursor to end of line)
      * Preparation for numeric theming ability (numerics will be looked up via an
        array defined inside of the theme)
      * Default IRC server is now irc.synchro.net, channel #synchronet
      * ircd.js is no longer vapourware (pass BBS user's password automatically)
      3dc1d985
      History
      * irc.js is now 'themeable' through -t, see irc-default.js, and irc-cyan.js
      cyan authored
        for examples on how to change the look of irc.js
      * Prefix on join/part/kick, etc no long shows the nick! part of the n!u@h
      * Tab completion bug fixed. (hitting tab wouldn't force cursor to end of line)
      * Preparation for numeric theming ability (numerics will be looked up via an
        array defined inside of the theme)
      * Default IRC server is now irc.synchro.net, channel #synchronet
      * ircd.js is no longer vapourware (pass BBS user's password automatically)
    irc.js 35.97 KiB
    // irc.js
    
    // Deuce's IRC client module for Synchronet
    // With the "Manny Mods".  :-)
    
    // $Id$
    
    const REVISION = "$Revision$".split(' ')[1];
    const SPACEx80 = "                                                                                ";
    const MAX_HIST = 50;
    
    load("sbbsdefs.js");
    load("nodedefs.js");
    load("sockdefs.js");	// SOCK_DGRAM
    
    load("irclib.js");
    
    // Global vars
    var irc_server="irc.synchro.net";
    var irc_port=6667;
    var default_channel="#synchronet";
    var connect_timeout=15;	// Seconds
    var connected=0;
    var quit=0;
    var nick=user.handle;
    var nicks=new Array();
    var loading=true;
    var init_passthru=console.ctrlkey_passthru;
    var real_names=true;
    console.ctrlkey_passthru=~(134217728);
    
    /* Command-line options go BEFORE command-line args */
    /* FIXME: there's got to be a better way to copy an array. */
    var irc_args = argv.join(" ").split(" ");
    var irc_theme = "irc-default.js";
    for (cmdarg=0;cmdarg<argc;cmdarg++) {
    	switch(argv[cmdarg]) {
    		case "-A":
    		case "-a":
    			real_names=false;
    			irc_args.shift();
    			break;
    		case "-T":
    		case "-t":
    			irc_theme=argv[++cmdarg];
    			irc_args.shift();
    			irc_args.shift();
    			break;
    		default:
    			break;
    	}
    }
    
    /* Where we store sysop-defined numeric formats */
    Numeric_Format = new Array;
    
    /* Load the defined theme. -- Cyan */
    load(irc_theme);
    
    /* Command-line args can override default server values */
    if(irc_args[0]!=undefined)
    	irc_server=irc_args[0];
    if(irc_args[1]!=undefined)
    	irc_port=Number(irc_args[1]);
    if(irc_args[2]!=undefined)
    	default_channel=irc_args[2];
    
    default_channel=default_channel.replace(/\s+/g,"_");
    
    sock=new Socket();
    history=new History();
    screen=new Screen();
    
    if(!sock.bind(0,server.interface_ip_address)) {
    	log(format("!IRC ERROR %d binding socket to %s"
    		,sock.last_error,server.interface_ip_address));
    	alert("socket error");
    	clean_exit();
    }
    
    // Connect
    if(!sock.connect(irc_server,irc_port)) {
    	log(format("!IRC connection to %s FAILED with error %d"
    		,irc_server,sock.last_error));
    	alert("system not available");
    	clean_exit();
    }
    
    sock.send("PASS "+user.security.password+"\r\n");	// for use with JS IRC server
    if (nick=="")
    	nick=user.alias;
    nick=nick.replace(/\s+/g,"_");
    sock.send("NICK "+nick+"\r\n");
    username=user.alias;
    username=username.replace(/\s+/g,"_");
    sock.send("USER "+username+" 0 * :"+(real_names?user.name:user.alias)+"\r\n");
    
    channels=new Channels();
    
    // Wait for welcome message...
    while(!connected)  {
    	if(sock.poll(connect_timeout))  {
    		wait_for(["433","422","376"]);
    		connected=1;
    	}
    	else  {
    		alert("Response timeout");
    		sock.close();
    		clean_exit();
    	}
    }
    
    // Main loop
    while(!quit)  {
    	if(!sock.is_connected || !connected)  {
    		alert("Lost connection");
    		sock.close();
    		clean_exit();
    	}
    	
    	if(!client.socket.is_connected)  {
    		sock.send("QUIT :Dropped Carrier.\r\n");
    		quit=1;
    		sock.close();
    		bbs.hangup();
    		clean_exit();
    	}
    
    	screen.update();
    	recieve_command();
    }
    sock.close();
    clean_exit();
    
    function handle_command(prefix,command,message)  {
    	var from_nick=null;
    	var full_message=null;
    	var tmp_str=null;
    	var tmp_str2=null;
    	var	i=0;
    
    	if (command.match(/^[0-9]+/) && Numeric_Format[command]) {
    		var narg = message.split(" ");
    		var narg_string = IRC_string(message);
    		return 0;
    	}
    
    	switch(command)  {
    		case "PING":
    			sock.send("PONG "+message[0]+"\r\n");
    			break;
    		case "NOTICE":
    			message.shift();
    			from_nick=get_highlighted_nick(prefix,message);
    			full_message=message.join(" ");
    			full_message=full_message.substr(1);
    			full_message=full_message.replace(/\x01/g,"");
    			screen.print_line(printf(NOTICE_FORMAT,from_nick,full_message));
    			break;
    		case "KICK":
    			tmp_str=message.shift();
    			tmp_str2=message.shift();
    			from_nick=get_nick(prefix);
    			full_message=message.join(" ").substr(1);
    			if(tmp_str2.toUpperCase()==nick.toUpperCase())  {
    				channels.part(tmp_str,"");
    			}
    			screen.print_line(printf(KICK_FORMAT,tmp_str2,tmp_str,full_message));
    			break;
    		case "PRIVMSG":
    			if(message[1].substr(0,2)==":\x01")  {
    				// CTCP
    				handle_ctcp(prefix,message);
    			}
    			else  {
    				if(prefix=="")
    					from_nick="[-]";
    				else  {
    					from_nick=get_highlighted_nick(prefix,message);
    					message[0]=message[0].toUpperCase();
    					if(channels.current != undefined)  {
    						if(message[0]==channels.current.name)  {
    							from_nick=printf(FROM_NICK_CURCHAN,from_nick);
    						}
    						else  {
    							from_nick=printf(MSG_FORMAT,from_nick);
    							if(message[0].slice(0,1)=="#" || message[0].slice(0,1)=="&")  {
    								from_nick=from_nick+"\x01N\x01C"+message[0]+":\x01N\x01W ";
    							}
    						}
    					}
    					else  {
    						from_nick=printf(FROM_NICK_CURCHAN,from_nick);
    					}
    				}
    				message.shift();
    				message[0]=message[0].substr(1);
    				screen.print_line(from_nick+" "+message.join(" "));
    			}
    			break;
    		case "JOIN":
    			from_nick=get_highlighted_nick(prefix,message);
    			tmp_str=get_nick(prefix);
    			if(tmp_str.toUpperCase()==nick.toUpperCase())  {
    				channels.joined(message[0].substr(1));
    			}
    			else  {
    				channels.nick_add(tmp_str,message[0].substr(1));
    			}
    			prefix=prefix.split("!")[1];
    			screen.print_line(printf(JOIN_FORMAT,from_nick,prefix,message[0].substr(1)));
    			break;
    		case "QUIT":
    			from_nick=get_highlighted_nick(prefix,message);
    			tmp_str=get_nick(prefix);
    			prefix=prefix.split("!")[1];
    			full_message=message.shift();
    			full_message=full_message.substr(1);
    			screen.print_line(printf(QUIT_FORMAT,from_nick,channels.current.display,full_message+" "+message.join(" ")));
    			channels.nick_quit(tmp_str);
    			break;
    		case "NICK":
    			from_nick=get_highlighted_nick(prefix,message);
    			tmp_str2=get_nick(prefix);
    			prefix=prefix.split("!")[1];
    			tmp_str=message.shift();
    			tmp_str=tmp_str.substr(1);
    			tmp_str=tmp_str.split("!",1)[0]
    			screen.print_line(printf(NICK_FORMAT,from_nick,tmp_str));
    			if(tmp_str2.toUpperCase()==nick.toUpperCase())  {
    				nick=tmp_str;
    				screen.update_statline();
    			}
    			channels.nick_change(tmp_str2,tmp_str);
    			break;
    		case "SQUIT":
    			if(prefix.slice(0,1)==":")  {
    				from_nick=get_nick(prefix);
    				tmp_str=message.shift();
    				tmp_str2=message.shift();
    				tmp_str2=tmp_str2.substr(1);
    				screen.print_line(printf(SQUIT_FROM_NICK,from_nick,tmp_str,tmp_str2+message.join(" ")));
    			}
    			else  {
    				tmp_str=message.shift();
    				tmp_str2=message.shift();
    				tmp_str2=tmp_str2.substr(1);
    				screen.print_line(SQUIT_FROM_SERVER,tmp_str,tmp_str2+message.join(" "));
    			}
    		case "PART":
    			from_nick=get_highlighted_nick(prefix,message);
    			tmp_str=get_nick(prefix);
    			prefix=prefix.split("!")[1];
    			screen.print_line(printf(PART_FORMAT,from_nick,prefix,message[0].substr(1)));
    			channels.nick_part(tmp_str,message[0].substr(1));
    			break;
    		case "MODE":
    			from_nick=get_highlighted_nick(prefix,message);
    			prefix=prefix.split("!")[1];
    			modestr="";
    			for (modeidx=1;modeidx<message.length;modeidx++) {
    				if (modeidx > 1)
    					modestr += " ";
    				modestr += message[modeidx];
    			}
    			screen.print_line(printf(MODE_FORMAT,from_nick,message[0],modestr));
    			break;
    		case "TOPIC":
    			from_nick=get_highlighted_nick(prefix,message);
    			tmp_str=message.shift();
    			tmp_str2=message.join(" ");
    			tmp_str2=tmp_str2.substr(1);
    			for(i=0;i<channels.length;i++)  {
    				if(tmp_str.toUpperCase()==channels.channel[i].name)  {
    					channels.channel[i].topic=tmp_str2;
    					screen.update_statline();
    					screen.print_line(printf(TOPIC_FORMAT,from_nick,tmp_str,tmp_str2));
    				}
    			}
    			break;
    
    		// Numeric reply codes.
    		// <word1> <word2> <word3> <word4> <word5> <word6> <word7> :Message
    		case "211":		// Trace Server
    			message.shift();
    			tmp_str=message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str2=message.shift();
    			tmp_str2="\x01H\x01C!! \x01N\x01C"+tmp_str+" "+tmp_str2.substr(1)+" "+message.join(" ")+"\x01N\x01W";
    			screen.print_line(tmp_str2);
    			break;
    
    		// <word1> <word2> <word3> <word4> <word5> <word6> :Message
    		case "352":		// WHO reply
    		case "206":		// Trace Server
    		case "213":		// Stats CLINE
    		case "214":		// Stats NLINE
    		case "215":		// Stats ILINE
    		case "216":		// Stats KLINE
    		case "218":		// Stats YLINE
    			message.shift();
    			tmp_str=message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str2=message.shift();
    			tmp_str2="\x01H\x01C!! \x01N\x01C"+tmp_str+" "+tmp_str2.substr(1)+" "+message.join(" ")+"\x01N\x01W";
    			screen.print_line(tmp_str2);
    			break;
    
    		// <word1> <word2> <word3> <word4> <word5> :Message
    		case "241":		// Stats LLINE
    			message.shift();
    			tmp_str=message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str2=message.shift();
    			tmp_str2="\x01H\x01C!! \x01N\x01C"+tmp_str+" "+tmp_str2.substr(1)+" "+message.join(" ")+"\x01N\x01W";
    			screen.print_line(tmp_str2);
    			break;
    
    		// <word1> <word2> <word3> <word4> :Message
    		case "311":		// WHOIS reply
    		case "314":		// WHOWAS reply
    		case "200":		// Trace Link
    		case "243":		// Stats OLINE
    		case "244":		// Stats HLINE
    			message.shift();
    			tmp_str=message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str2=message.shift();
    			tmp_str2="\x01H\x01C!! \x01N\x01C"+tmp_str+" "+tmp_str2.substr(1)+" "+message.join(" ")+"\x01N\x01W";
    			screen.print_line(tmp_str2);
    			break;
    
    		// <word1> <word2> <word3> :Message
    		case "324":		// Channel Modes
    		case "201":		// Trace Connecting
    		case "202":		// Trace Handshake
    		case "203":		// Trace Unknown
    		case "204":		// Trace Operator
    		case "205":		// Trace User
    		case "208":		// New type of trace
    		case "261":		// Trace LOG
    			message.shift();
    			tmp_str=message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str2=message.shift();
    			tmp_str2="\x01H\x01C!! \x01N\x01C"+tmp_str+" "+tmp_str2.substr(1)+" "+message.join(" ")+"\x01N\x01W";
    			screen.print_line(tmp_str2);
    			break;
    
    		// <word1> <word2> :Message
    		case "312":		// WHOISSERVER Reply
    		case "317":		// WHOISIDLE Reply
    		case "322":		// LIST data
    		case "341":		// Invite being sent
    		case "351":		// (server) VERSION reply
    		case "364":		// Links
    		case "367":		// Ban List
    		case "212":		// Stats Command
    			message.shift();
    			tmp_str=message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str2=message.shift();
    			tmp_str2="\x01H\x01C!! \x01N\x01C"+tmp_str+" "+tmp_str2.substr(1)+" "+message.join(" ")+"\x01N\x01W";
    			screen.print_line(tmp_str2);
    			break;
    
    		// <word> :Message
    		case "313":		// WHOISOPERATOR Reply
    		case "319":		// WHOISCHANNELS Reply
    		case "301":		// AWAY Reply
    		case "318":		// End of WHOIS Reply
    		case "369":		// End of WHOWAS Reply
    		case "321":		// LIST Start
    		case "342":		// Is Summoning.
    		case "315":		// End of WHO
    		case "365":		// End of LINKS
    		case "368":		// End of ban list
    		case "382":		// Rehashing
    		case "391":		// Time reply
    		case "219":		// End if stats
    		case "221":		// UMODE reply
    		case "252":		// # Operators online
    		case "253":		// # unknown connections
    		case "254":		// # channels
    		case "256":		// Admin info
    			message.shift();
    			tmp_str=message.shift();
    			tmp_str2=message.shift();
    			tmp_str2="\x01H\x01C!! \x01N\x01C"+tmp_str+" - "+tmp_str2.substr(1)+" "+message.join(" ")+"\x01N\x01W";
    			screen.print_line(tmp_str2);
    			break;
    			
    		case "376":		// MOTD End
    			if (loading) {
    				loading = false;
    				channels.join(default_channel);
    			}
    		// :Message
    		case "001":		// Sent on successful registration
    		case "002":		// Sent on successful registration
    		case "003":		// Sent on successful registration
    		case "004":		// Sent on successful registration
    		case "005":		// Sent on successful registration
    		case "265":             // Local Users
    		case "266":             // Global Users
    		case "381":		// You're OPER
    		case "302":		// USERHOST Reply
    		case "303":		// ISON Reply
    		case "305":		// You are no longer away
    		case "306":		// You are now marked as away
    		case "323":		// LIST end
    		case "371":		// INFO
    		case "374":		// End if info list
    		case "392":		// USERS Start
    		case "393":		// USERS
    		case "394":		// USERS End
    		case "395":		// No users
    		case "242":		// Stats Uptime
    		case "251":		// LUSER Client
    		case "255":		// # Clients / # Servers
    		case "257":		// Admin LOC 1
    		case "258":		// Admin LOC 2
    		case "259":		// Admin e-mail
    		case "375":		// MOTD Start
    		case "372":		// MOTD
    		case "333":		// Extended TOPIC info (apparently)
    			message.shift();
    			tmp_str=message.shift();
    			tmp_str="\x01H\x01C!! \x01N\x01C"+tmp_str.substr(1)+" "+message.join(" ")+"\x01N\x01W";
    			screen.print_line(tmp_str);
    			break;
    		
    		case "331":		// No Topic
    		case "332":		// Topic
    			message.shift();
    			tmp_str=message.shift();
    			tmp_str2=message.join(" ");
    			tmp_str2=tmp_str2.substr(1);
    			for(i=0;i<channels.length;i++)  {
    				if(tmp_str.toUpperCase()==channels.channel[i].name)  {
    					channels.channel[i].topic=tmp_str2;
    					screen.update_statline();
    				}
    			}
    			break;
    
    		case "353":		// Name reply
    			message.shift();
    			message.shift();
    			tmp_str=message.shift();
    			message[0]=message[0].substr(1);
    			for(i=0;i<message.length;i++)  {
    				if(message[i].slice(0,1)=="@" || message[i].slice(0,1)=="+")  {
    					message[i]=message[i].substr(1);
    				}
    			}
    			for(i=0;i<channels.length;i++)  {
    				if(tmp_str.toUpperCase()==channels.channel[i].name)  {
    					channels.channel[i].nick=message;
    				}
    			}
    			screen.print_line("\x01N\x01BPeople in "+tmp_str+" right now: "+message.join(" "));
    			break;
    			
    		case "366":		// End of Names
    			break;
    
    		// Error Codes
    		// <word1> <word2> :Message errors
    		case "441":		// Nick not on channel
    		case "443":		// User already on channel (invite)
    			message.shift();
    			tmp_str=message.shift();
    			tmp_str=tmp_str+" "+message.shift();
    			tmp_str2=message.shift();
    			tmp_str2="\x01H\x01R!! \x01N\x01R"+tmp_str+" "+tmp_str2.substr(1)+" "+message.join(" ")+"\x01N\x01W";
    			screen.print_line(tmp_str2);
    			break;
    
    		// <word> :Message errors.
    		case "433":		// Nickname already in use
    			message.shift();
    			nick=message.shift()+"_";
    			sock.send("NICK " + nick + "\r\n");
    			break;
    		case "401":		// No such nick
    		case "402":		// No such server
    		case "403":		// No such channel
    		case "404":		// Cannot send to channel
    		case "405":		// Too Many Channels
    		case "406":		// Was no suck nickname
    		case "407":		// Too many targets
    		case "413":		// No toplevel domain specified
    		case "414":		// Wildcard in TLD
    		case "421":		// Unknown Command
    		case "423":		// No admin info available
    		case "432":		// Erroneous Nick... bad chars
    		case "436":		// Nick collision KILL
    		case "442":		// You aren't on that channel
    		case "444":		// User not logged in (From SUMMON command)
    		case "461":		// Not enough params
    		case "467":		// Channel key already set
    		case "471":		// Channel is full (+l)
    		case "472":		// Unknown mode
    		case "473":		// Invide only channel (And YOU aren't invided)
    		case "474":		// Banned from channel
    		case "475":		// Bad channel key (+k)
    		case "482":		// Not ChanOP so can't do that.
    			message.shift();
    			tmp_str=message.shift();
    			tmp_str2=message.shift();
    			tmp_str2="\x01H\x01R!! \x01N\x01R"+tmp_str+" - "+tmp_str2.substr(1)+" "+message.join(" ")+"\x01N\x01W";
    			screen.print_line(tmp_str2);
    			break;
    			
    		// :Message errors.
    		case "422":		// MOTD is missing
    		case "409":		// No origin
    		case "411":		// No recipient
    		case "412":		// No text to send
    		case "424":		// File Error
    		case "431":		// No nickname given
    		case "445":		// SUMMON disabled
    		case "446":		// USERS disabled
    		case "451":		// You aren't registered
    		case "462":		// You're ALREADY registered
    		case "463":		// You aren't allowed to connect (HOST based)
    		case "464":		// Incorrect password
    		case "465":		// You are banned
    		case "481":		// You aren't an IRCOP so you can't do that.
    		case "483":		// You can't kill a server.
    		case "491":		// No O-lines for your host
    		case "501":		// Unknown MODE flag
    		case "502":		// Can't change other users mode
    			message.shift();
    			tmp_str=message.shift();
    			tmp_str="\x01H\x01R!! \x01N\x01R"+tmp_str.substr(1)+" "+message.join(" ")+"\x01N\x01W";
    			screen.print_line(tmp_str);
    			break;
    
    		default:
    			screen.print_line("\x01N\x01R"+prefix+" "+command+" "+message.join(" ")+"\x01N\x01W");
    	}
    }
    
    function recieve_command()  {
    	var prefix="";
    	var command=null;
    	var message;
    	if(sock.poll(0))  {
    		message=sock.recvline().split(" ");
    
    		if(message[0].substr(0,1)==":")  {
    			prefix=message.shift();
    		}
    		command=message.shift();
    		handle_command(prefix,command,message);
    	}
    }
    
    function wait_for(commands)  {
    	var prefix="";
    	var command=null;
    	var message="";
    	var i=0;
    
    	while(1)  {
    		if(!sock.is_connected)  {
    			alert("Lost connection");
    			sock.close();
    			clean_exit();
    		}
    
    		if(!client.socket.is_connected)  {
    			sock.send("QUIT :Dropped Carrier.\r\n");
    			quit=1;
    			sock.close();
    			bbs.hangup();
    			clean_exit();
    		}
    		if(sock.poll(0))  {
    			message=sock.recvline().split(" ");
    
    			if(message[0].substr(0,1)==":")  {
    				prefix=message.shift();
    			}
    			command=message.shift();
    			console.print(command);
    			handle_command(prefix,command,message);
    			for(i=0;i<commands.length;i++)  {
    				if(command==commands[i])  {
    					return command;
    				}
    			}
    		}
    		screen.update();
    	}
    }
    
    function send_command(command,param)  {
    	var params=[null];
    	var send_to=null;
    	var full_params;
    	var i=0;
    	var got="";
    
    	switch(command)  {
    		case "MSG":
    			params=param.split(" ");
    			send_to=params.shift();
    			sock.send("PRIVMSG "+send_to+" :"+params.join(" ")+"\r\n");
    			screen.print_line(send_to+"\x01H\x01C<\x01N\x01C-\x01N\x01W "+params.join(" "));
    			break;
    		case "X":
    		case "Q":
    		case "QUIT":
    			sock.send("QUIT :"+param+"\r\n");
    			quit=1;
    			sock.close();
    			clean_exit();
    			break;
    		case "J":
    		case "JOIN":
    			channels.join(param);
    			break;
    		case "ME":
    			if(channels.current==undefined)  {
    				screen.print_line("\x01H\x01RYou are not in a channel!\x01N\x01W");
    			}
    			else  {
    				channels.current.send("\x01ACTION "+param+"\x01");
    				screen.print_line("\x01N\x01B*\x01W "+nick+" "+param);
    			}
    			break;
    		case "CTCP":
    			params=param.split(" ");
    			send_to=params.shift();
    			full_params=params.join(" ");
    			full_params=full_params.toUpperCase();
    			sock.send("PRIVMSG "+send_to+" :\x01"+full_params+"\x01\r\n");
    			break;
    		case "PART":
    			channels.part(channels.current.name,param);
    			break;
    		case "N":
    		case "NEXT":
    			channels.index+=1;
    			if(channels.index>=channels.length)  {
    				channels.index=0;
    			}
    			screen.update_statline();
    			break;
    		case "P":
    		case "PREVIOUS":
    		case "PREV":
    			channels.index-=1;
    			if(channels.index<0)  {
    				channels.index=channels.length-1;
    			}
    			screen.update_statline();
    			break;
    		case "TOPIC":
    			sock.send("TOPIC "+channels.current.name+" :"+param+"\r\n");
    			break;
    		case "KICK":
    			if (param.substr(0,1) == '#' || param.substr(0,1) == '&')  {
    				sock.send(command+" "+param+"\r\n");
    			}
    			else  {
    				sock.send("KICK "+channels.current.name+" "+param+"\r\n");
    			}
    			break;
    		default:
    			if(command.slice(0,1)=="#" || command.slice(0,1)=="&")  {
    				for(i=0;i<channels.length;i++)  {
    					if(command.toUpperCase()==channels.channel[i].name)  {
    						channels.index=i;
    						screen.update_statline();
    					}
    				}
    			}
    			else  {
    				sock.send(command+" "+param+"\r\n");
    			}
    	}
    }
    
    function handle_ctcp(prefix,message)  {
    	var from_nick=null;
    	var to_nick=null;
    	var full_message=null;
    
    	ctcp_command=message[1].substr(2);
    	ctcp_command=ctcp_command.replace(/\x01/g,"");
    	switch(ctcp_command)  {
    		case "ACTION":
    			message.shift();
    			message.shift();
    			from_nick=get_highlighted_nick(prefix,message);
    			full_message=message.join(" ");
    			full_message=full_message.replace(/\x01/g,"");
    			screen.print_line("\x01N\x01B*\x01W "+from_nick+" "+full_message);
    			break;
    		case "FINGER":
    			from_nick=get_highlighted_nick(prefix,message);
    			to_nick=get_nick(prefix);
    			sock.send("NOTICE "+to_nick+" :\x01FINGER :"+user.name+" ("+user.alias+") Idle: "+user.timeout+"\x01\r\n");
    			screen.print_line(">"+from_nick+"<"+" CTCP FINGER Reply: "+user.name+" ("+user.alias+") Idle: "+user.timeout);
    			break;
    		case "VERSION":
    			from_nick=get_highlighted_nick(prefix,message);
    			to_nick=get_nick(prefix);
    			sock.send("NOTICE "+to_nick+" :\x01VERSION Synchronet IRC Module:"+REVISION+":Synchronet"+"\x01\r\n");
    			screen.print_line(">"+from_nick+"<"+" CTCP VERSION Reply: VERSION Synchronet IRC Module:"+REVISION+":Synchronet");
    			break;
    		case "PING":
    			message.shift();
    			message.shift();
    			from_nick=get_highlighted_nick(prefix,message);
    			to_nick=get_nick(prefix);
    			sock.send("NOTICE "+to_nick+" :\x01PING "+message.join(" ")+"\x01\r\n");
    			screen.print_line(">"+from_nick+"<"+" CTCP PING Reply.");
    			break;
    		case "TIME":
    			from_nick=get_highlighted_nick(prefix,message);
    			to_nick=get_nick(prefix);
    			sock.send("NOTICE "+to_nick+" :\x01TIME "+strftime("%A, %B %d, %l:%M:%S%p, %G %Z",time())+"\x01\r\n");
    			screen.print_line(">"+from_nick+"<"+" CTCP TIME Reply: "+strftime("%A, %B %d, %l:%M:%S%p, %G %Z",time() ));
    			break;
    	}
    }
    
    function get_highlighted_nick(prefix,message)  {
    	var nick_mentioned=0;
    	var from_nick=null;
    
    	// Check if your nick is in the text...
    	from_nick=prefix.substr(1);
    	from_nick=from_nick.split("!",1)[0];
    	re=new RegExp("\\b"+nick+"\\b","i");
    	for(j=0;j<message.length;j++)  {
    		if(message[j].search(re)>=0)  {
    			nick_mentioned=1;
    		}
    	}
    	if(nick_mentioned==1)  {
    		from_nick="\x01N\x01Y"+from_nick+"\x01N\x01W";
    	}
    	// Not sure if I have to do this... but it makes me feel better.
    	delete re;
    	return from_nick;
    }
    
    function get_nick(prefix)  {
    	// Check if your nick is in the text...
    	var to_nick=prefix.substr(1);
    	to_nick=to_nick.split("!",1)[0];
    	return to_nick;
    }
    
    function clean_exit()  {
    	console.ctrlkey_passthru=init_passthru;
    	exit();
    }
    
    // channel object
    function Channel(cname)  {
    	var got="";
    	this.topic="No topic set";
    	this.name=cname.toUpperCase();
    	this.display=cname;
    	this.topic="";
    	this.part=Channel_part;
    	this.send=Channel_send;
    	this.nick=new Array();
    	this.matchnick=Channel_matchnick;
    }
    
    function Channel_part(message)  {
    	sock.send("PART "+this.name+" :"+message+"\r\n");
    	screen.print_line("PART "+this.name+" "+message+"\r\n");
    	this.name=null;
    	this.display=null;
    	this.topic=null;
    	this.part=null;
    }
    
    function Channel_send(message)  {
    	sock.send("PRIVMSG "+this.name+" :"+message+"\r\n");
    }
    
    function Channel_matchnick(inline)  {
    	var i=0;
    	var j=0;
    	var count=0;
    	var tmp_str="\x01N\x01BMatching Nicks:";
    	var nick_var="";
    	var partial=inline.toUpperCase();
    	var matched="";
    	var start="";
    
    	if(partial.lastIndexOf(" ")!=-1)  {
    		partial=partial.substr(partial.lastIndexOf(" ")+1);
    		start=inline.slice(0,inline.lastIndexOf(" ")+1);
    	}
    	if(partial=="")  {
    		screen.print_line("\x01H\x01BNothing to match.\x01N\x01W");
    	}
    	for(i=0;i<this.nick.length;i++)  {
    		if(partial==this.nick[i].substr(0,partial.length).toUpperCase())  {
    			tmp_str=tmp_str+" "+nick[i];
    			nick_var=this.nick[i];
    			count++;
    			if(matched=="")  {
    				matched=nick_var.toUpperCase();
    			}
    			else  {
    				nick_var=nick_var.substr(0,matched.length);
    				for(j=nick_var.length;matched.substr(0,j) != nick_var.substr(0,j).toUpperCase();j--)  {}
    				nick_var=nick_var.substr(0,j);
    				matched=nick_var.toUpperCase();
    			}
    		}
    	}
    	if(count>1 && matched.length==partial.length)  {
    		screen.print_line(tmp_str+"\x01N\x01W");
    		return inline;
    	}
    	if(count<1)  {
    		screen.print_line("\x01H\x01BNo matching nicks.\x01N\x01W");
    		return inline;
    	}
    	if(partial==inline.toUpperCase())  {
    		if(count==1)  {
    			return nick_var+": ";
    		}
    		return nick_var;
    	}
    	if(count==1)  {
    		return start+nick_var+" ";
    	}
    	return start+nick_var;
    }
    
    // channels object
    function Channels()  {
    	this.length=0;
    	this.index=0;
    	this.channel=new Array();
    	this.join=Channels_join;
    	this.part=Channels_part;
    	this.joined=Channels_joined;
    	this.current getter=function() {return this.channel[this.index];};
    // Joining is not attempted until numeric 376 (End of MOTD)
    //	this.join(default_channel);
    	this.nick_change=Channels_nick_change;
    	this.nick_quit=Channels_nick_quit;
    	this.nick_part=Channels_nick_part;
    	this.nick_add=Channels_nick_add;
    }
    
    function Channels_nick_change(from,to)  {
    	var i=0;
    	var j=0;
    	
    	for(i=0;i<this.length;i++)  {
    		for(j=0;j<this.channel[i].nick.length;j++)  {
    			if(this.channel[i].nick[j].toUpperCase()==from.toUpperCase())  {
    				this.channel[i].nick[j]=to;
    			}
    		}
    	}
    }
    
    function Channels_nick_quit(nick)  {
    	var i=0;
    	var j=0;
    
    	for(i=0;i<this.length;i++)  {
    		for(j=0;j<this.channel[i].nick.length;j++)  {
    			if(this.channel[i].nick[j].toUpperCase()==nick.toUpperCase())  {
    				this.channel[i].nick.splice(j,1);
    			}
    		}
    	}
    }
    
    function Channels_nick_part(nick,cname)  {
    	var i=0;
    	var j=0;
    
    	for(i=0;i<this.length;i++)  {
    		if(cname.toUpperCase()==this.channel[i].name)  {
    			for(j=0;j<this.channel[i].nick.length;j++)  {
    				if(this.channel[i].nick[j].toUpperCase()==nick.toUpperCase())  {
    					this.channel[i].nick.splice(j,1);
    				}
    			}
    		}
    	}
    }
    
    function Channels_nick_add(nick,cname)  {
    	var i=0;
    	var j=0;
    
    	for(i=0;i<this.length;i++)  {
    		if(cname.toUpperCase()==this.channel[i].name)  {
    			this.channel[i].nick.push(nick);
    		}
    	}
    }
    
    function Channels_join(cname)  {
    	sock.send("JOIN "+cname+"\r\n");
    }
    
    function Channels_joined(cname)  {
    	this.channel[this.length]=new Channel(cname);
    	this.index=this.length;
    	this.length++;
    }
    
    function Channels_part(cname,message)  {
    	var i;
    
    	if(this.current==undefined)  {
    		return;
    	}
    	cname=this.current.name;
    	for(i=0;i<this.length;i++)  {
    		if(cname.toUpperCase()==this.channel[i].name)  {
    			this.channel[i].part(message);
    			this.channel.splice(i,1);
    			this.length -= 1;
    		}
    	}
    	if(this.index>=(this.length-1))  {
    		this.index=0;
    	}
    }
    
    // Screen object
    function Screen()  {
    	console.clear();
    
    	this.line=new Array(console.screen_rows-3);
    	this.rows=console.screen_rows-3;	// Title, Status, and input rows are not counted.
    	this.update_input_line=Screen_update_input_line;
    	this.print_line=Screen_print_line;
    	this.update_statline=Screen_update_statline;
    	this.statusline getter=function() {
    		// THIS NEEDS TO GO INTO THE SCREEN BUFFER!!! ToDo
    		bbs.nodesync();
    		if(connected)  {
    			if(channels != undefined)  {
    				if(channels.current != undefined)  {
    					return "\x01N\x014 Nick: "+nick+"   Channel: "+channels.current.display+SPACEx80.substr(0,79-19-nick.length-channels.current.display.length)+"\x01N\x010\x01W";
    				}
    			}
    		}
    		return "\x01N\x014 Nick: "+nick+"   Channel: No Channel"+SPACEx80.substr(0,79-29-nick.length)+"\x01N\x010\x01W";
    	};
    	this.topicline getter=function() {
    		if(connected)  {
    			if(channels != undefined)  {
    				if(channels.current != undefined)  {
    					return "\x01H\x01Y\x014"+channels.current.topic.substr(0,80)+SPACEx80.substr(0,(80-channels.current.topic.length)>0?(80-channels.current.topic.length):0)+"\x01N\x01W\x010";
    				}
    			}
    		}
    		return "\x01H\x01Y\x014No Topic"+SPACEx80.substr(0,72)+"\x01N\x01W\x010";
    	}
    	this.input_buffer="";
    	this.input_pos=0;
    	this.handle_key=Screen_handle_key;
    	this.update=Screen_update;
    	this.print_line("\1n\1hSynchronet \1cInternet Relay Chat \1wModule \1n" + REVISION + "\r\n");
    }
    
    function Screen_update_statline()  {
    	var cname="";
    	var topic="";
    
    	console.ansi_gotoxy(1,console.screen_rows-1);
    	if(channels.current==undefined)  {
    		cname="No channel";
    		topic="Not in channel";
    	}
    	else  {
    		cname=channels.current.display;
    		topic=channels.current.topic;
    	}
    	console.print(this.statusline);
    	console.crlf();
    	console.ansi_gotoxy(1,1);
    	console.clearline();
    	console.print(this.topicline);
    	this.update_input_line();
    }
    
    function Screen_print_line(line)  {
    	var i=0;
    	var lastspace=0;
    	var linestart=0;
    	var prev_colour="";
    	var last_colour="";
    	var cname="";
    	var topic="";
    
    	console.line_counter=0;	// defeat pause
    	console.ansi_gotoxy(1,console.screen_rows-1);
    	console.clearline();
    	console.ansi_gotoxy(1,console.screen_rows);
    	console.clearline();
    	console.ansi_gotoxy(1,console.screen_rows-1);
    	// Remove bold
    	line=line.replace(/\x02/g,"");
    	// mIRC colour codes
    	line=line.replace(/\x03([0-9\,]{1,5})/g,
    		function(str,p1,offset,s)  {
    			var p2;
    			var ending=null;
    			var codes=[null];
    			var ret=null;
    			
    			ending="";
    			codes=p1.split(",");
    			p1=codes[0];
    			codes.shift();
    			p2=codes[0];
    			codes.shift();
    			ending=","+codes.join(",");
    			if(p2==undefined)  {
    				p2="-1";
    			}
    			if(p2.length>2)  {
    				ending=p2.substr(2)+ending;
    				p2=p2.substr(0,2);
    			}
    			if(p1.length>2)  {
    				ending=p1.substr(2)+ending;
    				p1=p1.substr(0,2);
    			}
    			p1=Number(p1);
    			p2=Number(p2);
    			p1=p1 % 16;
    			if(p2>=0 && p2<=99)  {
    				p2=Number(p2) % 8;
    			}
    			else  {
    				p2=8;
    			}
    			switch(p1)  {
    				case 0:
    					ret="\x01H\x01W";
    					break;
    				case 1:
    					ret="\x01N\x01K";
    					break;
    				case 2:
    					ret="\x01N\x01B";
    					break;
    				case 3:
    					ret="\x01N\x01G";
    					break;
    				case 4:
    					ret="\x01N\x01R";
    					break;
    				case 5:
    					ret="\x01N\x01Y";
    					break;
    				case 6:
    					ret="\x01H\x01M";
    					break;
    				case 7:
    					// This is supposed to be ORANGE damnit!
    					ret="\x01H\x01R";
    					break;
    				case 8:
    					ret="\x01H\x01Y";
    					break;
    				case 9:
    					ret="\x01H\x01G";
    					break;
    				case 10:
    					ret="\x01N\x01C";
    					break;
    				case 11:
    					ret="\x01H\x01C";
    					break;
    				case 12:
    					ret="\x01H\x01B";
    					break;
    				case 13:
    					ret="\x01H\x01M";
    					break;
    				case 14:
    					ret="\x01H\x01K";
    					break;
    				case 15:
    					ret="\x01N\x01W";
    					break;
    				default:
    					ret="";
    			}
    			switch(p2)  {
    				case 0:
    					ret=ret+"\x017";
    					break;
    				case 1:
    					ret=ret+"\x010";
    					break;
    				case 2:
    					ret=ret+"\x014";
    					break;
    				case 3:
    					ret=ret+"\x012";
    					break;
    				case 4:
    					ret=ret+"\x011";
    					break;
    				case 5:
    					ret=ret+"\x013";
    					break;
    				case 6:
    					ret=ret+"\x015";
    					break;
    				case 7:
    					// This is supposed to be ORANGE damnit!
    					ret=ret+"\x016";
    					break;
    			}
    			return ret+ending;
    		}
    	);
    	if(line.length > 78)  {
    		// Word Wrap...
    		for(var j=0;j<=line.length;j++)  {
    			switch(line.charAt(j))  {
    				case "\x01":
    					last_colour=last_colour+line.substr(j,2);
    					j+=1;
    					break;
    				case " ":
    					lastspace=j;
    				default:
    					if(i>=78)  {
    						if(lastspace==linestart-1)  {
    							lastspace=j;
    						}
    						console.print(prev_colour+line.substring(linestart,lastspace+1));
    						prev_colour=last_colour;
    						console.crlf();
    						this.line.shift();
    						this.line.push(prev_colour+line.substring(linestart,lastspace+1));
    						linestart=lastspace+1;
    						j=lastspace;
    						i=0;
    					}
    					i+=1;
    			}
    		}
    	}
    	if(i<=78)  {
    		console.print(prev_colour+line.substr(linestart));
    		this.line.shift();
    		this.line.push(prev_colour+line.substr(linestart));
    		console.crlf();
    	}
    	if(!connected)  {
    		cname="No channel";
    		topic="Not in channel";
    	}
    	else if (channels==undefined)  {
    		cname="No channel";
    		topic="Not in channel";
    	}
    	else if (channels.current==undefined)  {
    		cname="No channel";
    		topic="Not in channel";
    	}
    	else  {
    		cname=channels.current.display;
    		topic=channels.current.topic;
    	}
    	console.print(this.statusline);
    	console.crlf();
    	console.ansi_gotoxy(1,1);
    	console.clearline();
    	console.print(this.topicline);
    	this.update_input_line();
    }
    
    function Screen_update_input_line()  {
    	var line_pos=this.input_pos;
    	var line_str=this.input_buffer;
    	var line_start=0;
    	var line_len=this.input_buffer.length;
    
    	if(line_len-this.input_pos < 39)  {
    		line_start=line_len-78;
    	}
    	else if (this.input_pos < 39)  {
    		line_start=0;
    	}
    	else  {
    		line_start=this.input_pos-39;
    	}
    	if(line_start<0)  {
    		line_start=0;
    	}
    	line_pos=this.input_pos-line_start;
    	line_str=this.input_buffer.substr(line_start,78);
    	if(line_start>0)  {
    		line_str='+'+line_str.substr(1);
    	}
    	if(line_start+78 < line_len)  {
    		line_str=line_str.slice(0,77)+'+';
    	}
    	
    	console.line_counter=0;	// defeat pause
    	console.ansi_gotoxy(1,console.screen_rows);
    	console.clearline();
    	printf("%s",line_str);
    	console.ansi_gotoxy(line_pos+1,console.screen_rows);
    }
    
    function Screen_update()  {
    	var key=console.inkey();
    	if(key!="")  {
    		this.handle_key(key);
    	}
    	else {
    		sleep(1);
    	}
    }
    
    function Screen_handle_key(key)  {
    	var commands=[null];
    	var command=null;
    	
    	switch(key)  {
    		case "\r":
    			if(this.input_buffer=="")  {
    				break;
    			}
    			history.addline(this.input_buffer);
    			while(history.line.length>MAX_HIST)  {
    				history.shift();
    			}
    			this.input_buffer=this.input_buffer.replace(/%C/g,"\x03");
    			this.input_buffer=this.input_buffer.replace(/%%/g,"%");
    			if(this.input_buffer.substr(0,1)=="/" && this.input_buffer.substr(1,1)!="/")  {
    				commands=this.input_buffer.split(" ");
    				command=commands.shift();
    				command=command.substr(1);
    				command=command.toUpperCase();
    				send_command(command,commands.join(" "));
    			}
    			else  {
    				if(this.input_buffer.substr(0,1)=="/")  {
    					this.input_buffer=this.input_buffer.substr(1);
    				}
    				if(channels.current==undefined)  {
    					this.print_line("\x01H\x01RYou are not in a channel!\x01N\x01W");
    				}
    				else  {
    					channels.current.send(this.input_buffer);
    					this.print_line("\x01N\x01M<\x01N\x01W"+nick+"\x01N\x01M>\x01N\x01W "+this.input_buffer);
    				}
    			}
    			this.input_buffer="";
    			this.input_pos=0;
    			this.update_input_line();
    			break;
    		case "\x08":
    			if(this.input_pos > 0)  {
    				this.input_buffer=this.input_buffer.slice(0,this.input_pos-1)+this.input_buffer.slice(this.input_pos);
    				this.input_pos--;
    				this.update_input_line();
    			}
    			break;
    		case "\x0b":
    			this.input_buffer=this.input_buffer+"%C";
    			this.input_pos+=2;
    			break;
    		case "\x1e":		// Up arrow
    			if(history.index==null)  {
    				history.incomplete=this.input_buffer;
    			}
    			this.input_buffer=history.previous;
    			this.input_pos=this.input_buffer.length;
    			this.update_input_line()
    			break;
    		case "\x0a":		// Down arrow
    			if(history.index==null)  {
    				history.incomplete=this.input_buffer;
    			}
    			this.input_buffer=history.next;
    			this.input_pos=this.input_buffer.length;
    			this.update_input_line()
    			break;
    		case "\x1d":		// Left arrow
    			if(this.input_pos > 0)  {
    				this.input_pos--;
    			}
    			this.update_input_line();
    			break;
    		case "\x02":		// Home
    			this.input_pos=0;
    			this.update_input_line()
    			break;
    		case "\x05":		// End
    			this.input_pos=this.input_buffer.length;
    			this.update_input_line()
    			break;
    		case "\x06":		// right arrow
    			if(this.input_pos < (this.input_buffer.length))  {
    				this.input_pos++;
    			}
    			this.update_input_line();
    			break;
    		case "\t":			// Tab
    			this.input_buffer=channels.current.matchnick(this.input_buffer);
    			// Always go to the end of the line on a tab. -- Cyan
    			var my_eol = this.input_buffer.length;
    			this.input_pos = my_eol;
    			this.update_input_line();
    			break;
    		default:
    			if(ascii(key)<ascii(' '))  {
    				if(ascii(key) != 27 && console.handle_ctrlkey!=undefined)  {
    					console.line_counter=0;	// defeat pause
    					console.ansi_gotoxy(1,console.screen_rows-1);
    					console.clearline();
    					console.ansi_gotoxy(1,console.screen_rows);
    					console.clearline();
    					console.ansi_gotoxy(1,console.screen_rows-1);
    					console.handle_ctrlkey(key,0); // for now
    					console.print(this.statusline);
    					console.crlf();
    					console.ansi_gotoxy(1,1);
    					console.clearline();
    					console.print(this.topicline);
    					this.update_input_line();
    				}
    			}
    			else  {
    				this.input_buffer=this.input_buffer.slice(0,this.input_pos)+key+this.input_buffer.slice(this.input_pos);
    				this.input_pos++;
    				this.update_input_line();
    			}
    	}
    }
    
    // History object
    function History()  {
    	this.index=0;
    	this.max=MAX_HIST;
    	this.line=new Array("");
    	this.addline=History_addline;
    	this.index=-1;
    	this.incomplete="";
    	this.next getter=function()  {
    		if(this.index==null)  {
    			this.index=this.line.length;
    		}
    		this.index+=1;
    		if(this.index>=this.line.length)  {
    			this.index=null;
    			return this.incomplete;
    		}
    		else  {
    			return this.line[this.index];
    		}
    	}
    	this.previous getter=function()  {
    		if(this.index==null)  {
    			this.index=this.line.length;
    		}
    		this.index-=1;
    		if(this.index<0)  {
    			this.index=0;
    		}
    		return this.line[this.index];
    	}
    }
    
    function History_addline(line)  {
    	this.line.push(line);
    	while(this.line.length>this.max)  {
    		this.line.shift();
    	}
    	this.index=this.line.length-1;
    	this.index=null;
    }