diff --git a/exec/irc.js b/exec/irc.js new file mode 100644 index 0000000000000000000000000000000000000000..ae7cef1d146557ddfca73d7c3f49cf3a60e8c1f0 --- /dev/null +++ b/exec/irc.js @@ -0,0 +1,1275 @@ +// sbbsimsg.js + +// Synchronet inter-bbs instant message module +// uses Finger and SMTP TCP/IP protocols + +// $Id$ + +const REVISION = "$Revision$".split(' ')[1]; +const SPACEx80 = " "; +const MAX_HIST = 50; + +load("sbbsdefs.js"); +load("nodedefs.js"); +load("sockdefs.js"); // SOCK_DGRAM + +// Global vars +//var irc_server="irc.lordlegacy.org"; +var irc_server="irc.thebbs.org"; +//var irc_server="irc.nexushosting.biz"; +var irc_port=6669; +var default_channel="&"+system.name; +var connect_timeout=15; // Seconds +var connected=0; +var quit=0; +var nick=user.handle; +var nicks=new Array(); + +default_channel=default_channel.replace(/\s+/g,"_"); + +sock=new Socket(); +history=new History(); +screen=new Screen(); + +// 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"); + exit(); +} + +sock.send("PASS "+user.security.password+"\r\n"); +screen.print_line("PASS ********"); +sock.send("NICK "+nick+"\r\n"); +screen.print_line("NICK "+nick); +username=user.alias; +username=username.replace(/\s+/g,"_"); +sock.send("USER "+username+" 0 * :"+user.name+"\r\n"); +screen.print_line("USER "+username+" 0 * :"+user.name); + +// Wait for welcome message... +while(!connected) { + if(sock.poll(connect_timeout)) { + wait_for(["433","422","376"]); + connected=1; + } + else { + alert("Response timeout"); + sock.close(); + exit(); + } +} + +channels=new Channels(); + +// Main loop +while(!quit) { + if(!sock.is_connected || !connected) { + alert("Lost connection"); + sock.close(); + exit(); + } + + if(!client.socket.is_connected) { + sock.send("QUIT :Dropped Carrier.\r\n"); + quit=1; + sock.close(); + bbs.hangup(); + exit(); + } + + screen.update(); + recieve_command(); +} +sock.close(); +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; + + 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("\x01N\x01R$\x01N\x01W"+from_nick+"\x01N\x01R$\x01N\x01W "+full_message); + break; + case "KICK": + tmp_str=message.shift(); + from_nick=message.shift(); + full_message=message.join(" ").substr(1); + tmp_str2=get_nick(prefix); + if(tmp_str2.toUpperCase()==nick.toUpperCase()) { + channels.part(tmp_str,""); + } + screen.print_line("\x01N\x01R-\x01H\x01R- "+tmp_str2+" has been kicked from "+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="\x01N\x01B<\x01N\x01W"+from_nick+"\x01N\x01B>\x01N\x01W"; + } + else { + from_nick=from_nick+"\x01N\x01C-\x01H\x01C>\x01N\x01W"; + if(message[0].slice(0,1)=="#" || message[0].slice(0,1)=="&") { + from_nick=from_nick+"\x01N\x01C"+message[0]+":\x01N\x01W "; + } + } + } + else { + from_nick="\x01N\x01B<\x01N\x01W"+from_nick+"\x01N\x01B>\x01N\x01W"; + } + } + 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.substr(1); + screen.print_line("\x01N\x01C"+from_nick+" ("+prefix+")"+" has joined "+message[0].substr(1)); + break; + case "QUIT": + from_nick=get_highlighted_nick(prefix,message); + tmp_str=get_nick(prefix); + prefix=prefix.substr(1); + full_message=message.shift(); + full_message=full_message.substr(1); + screen.print_line(from_nick+" has quit "+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.substr(1); + tmp_str=message.shift(); + tmp_str=tmp_str.substr(1); + tmp_str=tmp_str.split("!",1)[0] + screen.print_line(from_nick+" has changed nick to "+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(from_nick+" has disconnected the server "+tmp_str+" ( "+tmp_str2+message.join(" ")+" )"); + } + else { + tmp_str=message.shift(); + tmp_str2=message.shift(); + tmp_str2=tmp_str2.substr(1); + screen.print_line("The server "+tmp_str+" has disconnected ( "+tmp_str2+message.join(" ")+" )"); + } + case "PART": + from_nick=get_highlighted_nick(prefix,message); + tmp_str=get_nick(prefix); + prefix=prefix.substr(1); + screen.print_line(from_nick+" ("+prefix+")"+" has left "+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.substr(1); + tmp_str=from_nick+" has set MODE "+message[0]+" for "+message[1]; + if(message.length>1) { + tmp_str=tmp_str+" Limit: "+message[1]; + } + if(message.length>2) { + tmp_str=tmp_str+" User: "+message[2]; + } + if(message.length>3) { + tmp_str=tmp_str+" ("+message[3]+")"; + } + screen.print_line(tmp_str); + 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(from_nick+" has changed the topic of "+tmp_str+" to '"+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; + + // :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 "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 "376": // MOTD End + 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 + 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(); + exit(); + } + + if(!client.socket.is_connected) { + sock.send("QUIT :Dropped Carrier.\r\n"); + quit=1; + sock.close(); + bbs.hangup(); + 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(); + 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; + 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; +} + + +// 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 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+" "+this.nick[i]; + nick=this.nick[i]; + count++; + if(matched=="") { + matched=nick.toUpperCase(); + } + else { + nick=nick.substr(0,matched.length); + for(j=nick.length;matched.substr(0,j) != nick.substr(0,j).toUpperCase();j--) {} + nick=nick.substr(0,j); + matched=nick.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+": "; + } + return nick; + } + if(count==1) { + return start+nick+" "; + } + return start+nick; +} + +// 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];}; + 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()==channels.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()==channels.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() { + 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.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() { + console.line_counter=0; // defeat pause + console.ansi_gotoxy(1,console.screen_rows); + console.clearline(); + if(this.input_buffer.length>78) { + printf("+"); + } + printf("%s",this.input_buffer.substr(-78)); +} + +function Screen_update() { + var key=console.inkey(); + if(key!="") { + this.handle_key(key); + } +} + +function Screen_handle_key(key) { + var commands=[null]; + var command=null; + + switch(key) { + case "\r": + if(screen.input_buffer=="") { + break; + } + history.addline(screen.input_buffer); + while(history.line.length>MAX_HIST) { + history.shift(); + } + screen.input_buffer=screen.input_buffer.replace(/%C/g,"\x03"); + screen.input_buffer=screen.input_buffer.replace(/%%/g,"%"); + if(screen.input_buffer.substr(0,1)=="/" && screen.input_buffer.substr(1,1)!="/") { + commands=screen.input_buffer.split(" "); + command=commands.shift(); + command=command.substr(1); + command=command.toUpperCase(); + send_command(command,commands.join(" ")); + } + else { + if(screen.input_buffer.substr(0,1)=="/") { + screen.input_buffer=screen.input_buffer.substr(1); + } + if(channels.current==undefined) { + screen.print_line("\x01H\x01RYou are not in a channel!\x01N\x01W"); + } + else { + channels.current.send(screen.input_buffer); + } + screen.print_line("\x01N\x01M<\x01N\x01W"+nick+"\x01N\x01M>\x01N\x01W "+screen.input_buffer); + } + screen.input_buffer=""; + screen.update_input_line(); + break; + case "\x08": + screen.input_buffer=screen.input_buffer.slice(0,-1); + screen.update_input_line(); + break; + case "\x0b": + screen.input_buffer=screen.input_buffer+"%C"; + break; + case "\x1e": // Up arrow + if(history.index==null) { + history.incomplete=screen.input_buffer; + } + screen.input_buffer=history.previous; + screen.update_input_line() + break; + case "\x0a": // Down arrow + if(history.index==null) { + history.incomplete=screen.input_buffer; + } + screen.input_buffer=history.next; + screen.update_input_line() + break; + case "\x1d": // Left arrow + channels.index-=1; + if(channels.index<0) { + channels.index=channels.length-1; + } + screen.update_statline(); + break; + case "\x06": // right arrow + channels.index+=1; + if(channels.index>=channels.length) { + channels.index=0; + } + screen.update_statline(); + break; + case "\t": // Tab + screen.input_buffer=channels.current.matchnick(screen.input_buffer); + screen.update_input_line(); + break; + default: + screen.input_buffer=screen.input_buffer+key; + screen.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; +}