-
Rob Swindell authored
Seen disconnected users "stuck" in irc.js for long periods of time. Hopefully this helps.
Rob Swindell authoredSeen disconnected users "stuck" in irc.js for long periods of time. Hopefully this helps.
irc.js 38.17 KiB
// irc.js
// Deuce's IRC client module for Synchronet
// With the "Manny Mods". :-)
// disable auto-termination.
var old_auto_terminate=js.auto_terminate;
js.on_exit("js.auto_terminate=old_auto_terminate");
js.auto_terminate=false;
const REVISION = "1.62";
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=30; // Seconds
var connected=0;
var quit=0;
var nick=user.handle;
var nicks=new Array();
var loading=true;
var real_names=true;
js.on_exit("console.ctrlkey_passthru = " + console.ctrlkey_passthru);
console.ctrlkey_passthru=~(134217728);
var pmode = 0;
var options = load('modopts.js', 'irc');
var utf8_support = console.term_supports(USER_UTF8);
if(options && options.utf8_support === false)
utf8_support = false;
if(utf8_support)
pmode |= P_UTF8;
log("utf8_support: " + utf8_support);
// Commands to send...
var client_cmds = {
'PASS':{minparam:1,maxparam:1}, // Must be sent before NICK/USER
'NICK':{minparam:1,maxparam:1},
'USER':{minparam:4,maxparam:4},
'OPER':{minparam:2,maxparam:2},
'QUIT':{minparam:0,maxparam:1},
'JOIN':{minparam:1,maxparam:2},
'PART':{minparam:1,maxparam:2},
'TOPIC':{minparam:1,maxparam:2},
'NAMES':{minparam:1,maxparam:1},
'LIST':{minparam:1,maxparam:2},
'MOTD':{minparam:0,maxparam:1},
'VERSION':{minparam:0,maxparam:1},
'ADMIN':{minparam:0,maxparam:1},
'CONNECT':{minparam:1,maxparam:3},
'TIME':{minparam:0,maxparam:1},
'STATS':{minparam:0,maxparam:2},
'INFO':{minparam:0,maxparam:1},
'MODE':{minparam:1,maxparam:Infinity},
'PRIVMSG':{minparam:2,maxparam:2},
'NOTICE':{minparam:2,maxparam:2},
'USERHOST':{minparam:1,maxparam:Infinity},
'KILL':{minparam:1,maxparam:2},
'SQUIT':{minparam:2,maxparam:2},
'INVITE':{minparam:2,maxparam:2},
'KICK':{minparam:2,maxparam:3},
'LINKS':{minparam:0,maxparam:2},
'TRACE':{minparam:0,maxparam:1},
'WHO':{minparam:0,maxparam:2},
'WHOIS':{minparam:1,maxparam:2},
'WHOWAS':{minparam:1,maxparam:3},
'PING':{minparam:1,maxparam:2},
'PONG':{minparam:1,maxparam:2},
'AWAY':{minparam:0,maxparam:1},
'REHASH':{minparam:0,maxparam:0},
'RESTART':{minparam:0,maxparam:0},
'SUMMON':{minparam:1,maxparam:3},
'USERS':{minparam:0,maxparam:1},
'WALLOPS':{minparam:1,maxparam:1},
'ISON':{minparam:1,maxparam:Infinity},
'LUSERS':{minparam:0,maxparam:2},
'SERVLIST':{minparam:0,maxparam:2},
'SQUERY':{minparam:2,maxparam:2},
'DIE':{minparam:0,maxparam:0},
};
/* Command-line options go BEFORE command-line args */
var irc_theme = "irc-default.js";
var user_password = user.security.password;
ARGPARSE: for (cmdarg=0;cmdarg<argc;cmdarg++) {
switch(argv[cmdarg]) {
case "-A":
case "-a":
real_names=false;
break;
case "-T":
case "-t":
irc_theme=argv[++cmdarg];
break;
case "-P":
case "-p":
user_password = argv[++cmdarg];
break;
default:
break ARGPARSE;
}
}
/* Load the defined theme. -- Cyan */
load(irc_theme);
/* Command-line args can override default server values */
if(argv[cmdarg]!=undefined)
irc_server=argv[cmdarg++];
if(argv[cmdarg]!=undefined)
irc_port=Number(argv[cmdarg++]);
if(argv[cmdarg]!=undefined)
default_channel=argv[cmdarg++];
default_channel=default_channel.replace(/\s+/g,"_");
sock=new Socket();
sock.bind(0,server.interface_ip_address); // Use globally defined intereface in sbbs.ini
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(irc_server+" not available");
clean_exit();
}
if(user_password)
send_cmd("PASS", user_password);
if (nick=="")
nick=user.alias;
nick=nick.replace(/\s+/g,"_");
send_cmd("NICK", nick);
username=user.alias;
username=username.replace(/\s+/g,"_");
send_cmd("USER", username+" 0 * :"+(real_names?user.name:user.alias)+" ("+client.ip_address+")");
channels=new Channels();
// Wait for welcome message...
while(!connected) {
if(sock.poll(connect_timeout)) {
if(wait_for(["433","432","422","376"]) == 432) {
log(LOG_WARNING, "Nick rejected with error 432, auto-fixing nick");
nick = "_" + nick;
send_cmd("NICK", nick);
} else
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 || !bbs.online) {
send_cmd("QUIT",":Dropped Carrier");
quit=1;
sock.close();
bbs.hangup();
clean_exit();
}
if(js.terminated) {
send_cmd("QUIT",":Client terminated.");
quit=1;
sock.close();
bbs.hangup();
clean_exit();
}
if(bbs.get_time_left && !bbs.get_time_left()) {
send_cmd("QUIT",":Out of time.");
quit=1;
sock.close();
clean_exit();
}
if(sock.poll(.01)) {
receive_command();
screen.update(0);
}
else
screen.update(100);
}
sock.close();
clean_exit();
function send_cmd(command, params)
{
var snd;
var plist;
var pcnt = 0;
var cmd;
if (params === undefined)
plist = [];
else {
if (params[0] == ':')
params = params.substr(1);
plist = params.split(/ \:?/);
}
command = command.toUpperCase();
cmd = client_cmds[command];
if (cmd === undefined) {
screen.print_line("\x01H\x01R!! \x01N\x01R"+command+" not supported.\x01N\x01W");
return;
}
snd = command;
while (plist.length) {
snd += ' ';
pcnt++;
if (pcnt == cmd.maxparam && plist.length > 1)
snd += ':';
snd += plist.shift();
}
if (pcnt < cmd.minparam) {
screen.print_line("\x01H\x01R!! \x01N\x01R"+command+" requires at least "+cmd.minparam+" parameters\x01N\x01W");
return;
}
if(!utf8_support)
snd = utf8_encode(snd);
sock.send(snd+"\r\n");
}
function handle_command(tag, 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":
send_cmd("PONG",message.join(' '));
break;
case "NOTICE":
message.shift(); // Target
from_nick=get_highlighted_nick(prefix,message);
full_message=message.join(" ");
full_message=full_message.replace(/\x01/g,"");
screen.print_line(format(NOTICE_FORMAT,from_nick,full_message));
break;
case "KICK":
tmp_str=message.shift(); // Channel
tmp_str2=message.shift(); // User
from_nick=get_nick(prefix);
full_message=message.join(" ");
if(tmp_str2.toUpperCase()==nick.toUpperCase()) {
channels.part(tmp_str,"");
}
screen.print_line(format(KICK_FORMAT,tmp_str2,tmp_str,full_message));
break;
case "PRIVMSG":
if(message[1][0]=="\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=format(FROM_NICK_CURCHAN,from_nick);
}
else {
from_nick=format(MSG_FORMAT,from_nick);
if(message[0][0]=="#" || message[0][0]=="&") {
from_nick=from_nick+"\x01N\x01C"+message[0]+":\x01N\x01W ";
}
else
console.beep();
}
}
else {
from_nick=format(FROM_NICK_CURCHAN,from_nick);
}
}
message.shift(); // Receiver
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]);
}
else {
channels.nick_add(tmp_str,message[0]);
}
prefix=prefix.split("!")[1];
screen.print_line(format(JOIN_FORMAT,from_nick,prefix,message[0]));
break;
case "QUIT":
from_nick=get_highlighted_nick(prefix,message);
tmp_str=get_nick(prefix);
prefix=prefix.split("!")[1];
full_message=message.shift();
screen.print_line(format(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.split("!",1)[0]
screen.print_line(format(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.length > 0) {
from_nick=get_nick(prefix);
tmp_str=message.shift();
tmp_str2=message.shift();
screen.print_line(format(SQUIT_FROM_NICK,from_nick,tmp_str,tmp_str2+message.join(" ")));
}
else {
tmp_str=message.shift();
tmp_str2=message.shift();
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(format(PART_FORMAT,from_nick,prefix,message[0]));
channels.nick_part(tmp_str,message[0]);
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(format(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(" ");
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(format(TOPIC_FORMAT,from_nick,tmp_str,tmp_str2));
}
}
break;
// Numeric reply codes.
case "211": // Trace Server
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
case "241": // Stats LLINE
case "311": // WHOIS reply
case "314": // WHOWAS reply
case "367": // Ban List
case "200": // Trace Link
case "243": // Stats OLINE
case "244": // Stats HLINE
case "317": // WHOISIDLE Reply
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
case "312": // WHOISSERVER Reply
case "322": // LIST data
case "341": // Invite being sent
case "351": // (server) VERSION reply
case "364": // Links
case "212": // Stats Command
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
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(); // Client
screen.print_line("\x01H\x01C!! \x01N\x01C"+message.join(" ")+"\x01N\x01W");
break;
// Things that actually need dealing with...
case "376": // MOTD End
if (loading) {
loading = false;
channels.join(default_channel);
}
message.shift(); // Client
screen.print_line("\x01H\x01C!! \x01N\x01C"+message.join(" ")+"\x01N\x01W");
break;
case "331": // No Topic
case "332": // Topic
for(i=0;i<channels.length;i++) {
if(message[1].toUpperCase()==channels.channel[i].name) {
channels.channel[i].topic=message[2];
screen.update_statline();
}
}
break;
case "353": // Name reply
message.shift(); // Client
message.shift(); // Symbol
tmp_str=message.shift().toUpperCase(); // Channel
message = message[0].split(' ');
for(i=0;i<message.length;i++) {
switch(message[i][0]) {
case '~': // Founder
case '&': // Protected
case '@': // Op
case '%': // Half-op
case '+': // Voice
message[i]=message[i].substr(1);
break;
}
}
for(i=0;i<channels.length;i++) {
if(tmp_str == channels.channel[i].name) {
channels.channel[i].nick=message;
}
}
screen.print_line("\x01N\x01B\1hPeople in "+tmp_str+" right now: "+message.join(" "));
break;
case "366": // End of Names
break;
// Error Codes
case "433": // Nickname already in use
message.shift(); // Client
nick=message.shift()+"_";
send_cmd("NICK", nick);
break;
// <word1> <word2> :Message errors
case "441": // Nick not on channel
case "443": // User already on channel (invite)
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.
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(); // Client
if (message.length > 1) {
tmp_str=message.shift();
screen.print_line("\x01H\x01R!! \x01N\x01R"+tmp_str+" - "+message.join(" ")+"\x01N\x01W");
}
else {
screen.print_line("\x01H\x01R!! \x01N\x01R"+message.join(" ")+"\x01N\x01W");
}
break;
default:
screen.print_line("\x01N\x01R"+prefix+" "+command+" "+message.join(" ")+"\x01N\x01W");
}
}
function get_command()
{
var tag = "";
var prefix="";
var command=null;
var line;
var message = [];
var i;
if(sock.poll(0)) {
line=sock.recvline();
if(!line)
return;
if(!utf8_support)
line = utf8_decode(line);
if (line[0] == '@') {
tag = line.slice(1, line.indexOf(" "));
line = line.substr(tag.length + 2);
}
if (line[0] == ':') {
prefix = line.slice(1, line.indexOf(" "));
line = line.substr(prefix.length + 2);
}
message.push(tag);
message.push(prefix);
while (line.length > 0) {
if (line[0] == ':') {
message.push(line.substr(1));
line = '';
}
else {
i = line.indexOf(' ');
if (i == -1) {
message.push(line);
line = '';
}
else {
message.push(line.slice(0, i));
line = line.substr(i + 1);
}
}
}
return message;
}
}
function receive_command() {
var tag = "";
var prefix="";
var command=null;
var message;
var message = get_command();
if (message == undefined)
return;
tag = message.shift();
prefix = message.shift();
command = message.shift();
handle_command(tag, prefix, command, message);
}
function wait_for(commands) {
var tag = '';
var prefix="";
var command=null;
var message="";
var i=0;
while(bbs.online && sock.poll(connect_timeout)) {
if(!sock.is_connected) {
alert("Lost connection");
sock.close();
clean_exit();
}
if(!client.socket.is_connected) {
send_cmd("QUIT", ":Dropped Carrier.");
quit=1;
sock.close();
bbs.hangup();
clean_exit();
}
message = get_command();
if (message != undefined) {
tag = message.shift();
prefix = message.shift();
command = message.shift();
handle_command(tag,prefix,command,message);
for(i=0;i<commands.length;i++) {
if(command==commands[i]) {
return command;
}
}
}
// Don't handle user input at this point!
// screen.update();
}
alert("Connection timed out");
sock.close();
clean_exit();
}
function in_a_channel() {
if(channels.current==undefined) {
screen.print_line("\x01H\x01RYou are not in a channel!\x01N\x01W");
return false;
}
return true;
}
function send_command(command,param) {
var params=[null];
var send_to=null;
var full_params;
var i=0;
var got="";
switch(command) {
case "HELP":
const fmt = "/%-25s %s";
screen.print_line(format(fmt, "help", "Display this list"));
screen.print_line(format(fmt, "q[uit]", "Leave IRC Module " + REVISION));
screen.print_line(format(fmt, "me <text>", "Send an action message"));
screen.print_line(format(fmt, "quote <text>", "Send a literal message"));
screen.print_line(format(fmt, "msg <nick>", "Send a private message"));
screen.print_line(format(fmt, "j[oin] <#channel>", "Join a channel"));
screen.print_line(format(fmt, "n[ext]", "Switch to next channel"));
screen.print_line(format(fmt, "p[revious]", "Switch to previous channel"));
screen.print_line(format(fmt, "part", "Leave current channel"));
screen.print_line(format(fmt, "topic [#channel] <text>", "Set channel topic"));
screen.print_line(format(fmt, "kick [nick]", "Kick a user from channel"));
break;
case "MSG":
params=param.split(" ");
send_to=params.shift();
send_cmd("PRIVMSG", send_to+" :"+params.join(" "));
screen.print_line(send_to+"\x01H\x01C<\x01N\x01C-\x01N\x01W "+params.join(" "));
break;
case "X":
case "Q":
case "QUIT":
send_cmd("QUIT", param);
quit=1;
sock.close();
clean_exit();
break;
case "J":
case "JOIN":
channels.join(param);
break;
case "ME":
if(in_a_channel()) {
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();
send_cmd("PRIVMSG", send_to+" :\x01"+full_params+"\x01");
break;
case "PART":
// If the user specifies a channel, this SHOULD part that channel,
// not the current one.
if(in_a_channel())
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":
if(in_a_channel()) {
if (param.substr(0,1) == '#' || param.substr(0,1) == '&') {
send_cmd(command, param);
}
else {
send_cmd(command, channels.current.name+" "+param);
}
}
break;
case "KICK":
if(in_a_channel()) {
if (param.substr(0,1) == '#' || param.substr(0,1) == '&') {
send_cmd(command, param);
}
else {
send_cmd(command, channels.current.name+" "+param);
}
}
break;
case "QUOTE":
if (param.length)
sock.send(param+"\r\n");
break;
default:
if(command[0]=="#" || command[0]=="&") {
for(i=0;i<channels.length;i++) {
if(command.toUpperCase()==channels.channel[i].name) {
channels.index=i;
screen.update_statline();
}
}
}
else {
send_cmd(command, param);
}
}
}
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);
send_cmd("NOTICE", to_nick+" :\x01FINGER :"+user.name+" ("+user.alias+") Idle: "+user.timeout+"\x01");
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);
send_cmd("NOTICE", to_nick+" :\x01VERSION Synchronet IRC Module:"+REVISION+":Synchronet"+"\x01");
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);
send_cmd("NOTICE", to_nick+" :\x01PING "+message.join(" ")+"\x01");
screen.print_line(">"+from_nick+"<"+" CTCP PING Reply.");
break;
case "TIME":
from_nick=get_highlighted_nick(prefix,message);
to_nick=get_nick(prefix);
send_cmd("NOTICE", to_nick+" :\x01TIME "+strftime("%A, %B %d, %I:%M:%S%p, %Y %Z",time())+"\x01");
screen.print_line(">"+from_nick+"<"+" CTCP TIME Reply: "+strftime("%A, %B %d, %I:%M:%S%p, %Y %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;
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;
to_nick=prefix.split("!",1)[0];
return to_nick;
}
function clean_exit() {
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) {
send_cmd("PART", this.name+" :"+message);
screen.print_line("PART "+this.name+" "+message);
this.name=null;
this.display=null;
this.topic=null;
this.part=null;
}
function Channel_send(message) {
send_cmd("PRIVMSG", this.name+" :"+message);
}
function Channel_matchnick(nickpart) {
var i=0;
var j=0;
var count=0;
var tmp_str="\x01N\x01BMatching Nicks:";
var nick_var="";
var partial=nickpart.toUpperCase();
var matched="";
var start="";
if(partial=="") {
screen.print_line("\x01H\x01BNothing to match.\x01N\x01W");
return null;
}
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_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) {
screen.print_line("\x01H\x01BNo matching nicks.\x01N\x01W");
return null;
}
if(count>1 && matched.length==partial.length) {
screen.print_line(tmp_str+"\x01N\x01W");
return null;
}
return 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.__defineGetter__("current", 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.channel.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.channel.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.channel.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.channel.length;i++) {
if(cname.toUpperCase()==this.channel[i].name) {
this.channel[i].nick.push(nick);
}
}
}
function Channels_join(cname) {
send_cmd("JOIN", cname);
}
function Channels_joined(cname) {
this.index=this.channel.length;
this.channel[this.channel.length]=new Channel(cname);
this.length++;
}
function Channels_part(cname,message) {
var i;
if(this.current==undefined) {
return;
}
cname=this.current.name;
for(i=0;i<this.channel.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.channel.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.__defineGetter__("statusline", function() {
// THIS NEEDS TO GO INTO THE SCREEN BUFFER!!! ToDo
bbs.nodesync();
if(connected) {
if(channels != undefined) {
if(channels.current != undefined) {
var nick_chan="";
nick_char=format("\x01N\x014 Nick: %s Channel: %s (%d)",nick,channels.current.display,channels.current.nick.length)+SPACEx80;
return nick_char.substr(0,67)+" /help for help \x01N\x010\x01W";
}
}
}
return "\x01N\x014 Nick: "+nick+" Channel: No Channel (0)"+SPACEx80.substr(0,79-49-nick.length)+" /help for help \x01N\x010\x01W";
});
this.__defineGetter__("topicline", function() {
if(connected) {
if(channels != undefined) {
if(channels.current != undefined) {
if(channels.current.topic != undefined && channels.current.topic != '') {
return "\x01H\x01Y\x014"+channels.current.topic.substr(0,79)+SPACEx80.substr(0,(79-channels.current.topic.length)>0?(79-channels.current.topic.length):0)+"\x01N\x01W\x010";
}
}
}
}
return "\x01H\x01Y\x014No Topic"+SPACEx80.substr(0,71)+"\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-2);
if(channels.current==undefined) {
cname="No channel";
topic="Not in channel";
}
else {
cname=channels.current.display;
topic=channels.current.topic;
}
console.print(this.topicline, pmode);
console.crlf();
console.print(this.statusline, pmode);
console.crlf();
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-2);
// 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;
}
);
// Empty color code means remove all color formatting
line=line.replace(/\x03/g,DEFAULT_COLOR);
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), pmode);
prev_colour=last_colour;
console.cleartoeol();
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), pmode);
console.cleartoeol();
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.topicline, pmode);
console.crlf();
console.print(this.statusline, pmode);
console.crlf();
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();
console.print(line_str, pmode);
console.ansi_gotoxy(line_pos+1,console.screen_rows);
}
function Screen_update(wait) {
while(bbs.online) {
var key=console.inkey(wait);
if(key!="")
this.handle_key(key);
else
break;
}
}
function Screen_handle_key(key) {
var commands=[null];
var command=null;
var nickmatch="";
var lastspace=0;
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 "\x7f":
if(this.input_buffer.length > this.input_pos) {
this.input_buffer=this.input_buffer.slice(0,this.input_pos)+this.input_buffer.slice(this.input_pos+1);
this.update_input_line();
break;
}
/* Fall-through - delete at end of line is backspace */
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
lastspace=this.input_buffer.lastIndexOf(" ",this.input_pos);
nickmatch=channels.current.matchnick(this.input_buffer.substr(lastspace+1,this.input_pos-lastspace-1));
if(nickmatch != null) {
this.input_buffer=this.input_buffer.substr(0,lastspace+1)+nickmatch+(lastspace==-1?": ":" ")+this.input_buffer.substr(this.input_pos);
this.input_pos=lastspace+2+nickmatch.length;
if(lastspace==-1)
this.input_pos++;
}
else
console.beep();
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-2);
console.clearline();
console.handle_ctrlkey(key,0); // for now
console.print(this.topicline, pmode);
console.crlf();
console.print(this.statusline, pmode);
console.crlf();
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.__defineGetter__("next", 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.__defineGetter__("previous", 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;
}