diff --git a/xtrn/mrc/mrc-client.example.ini b/xtrn/mrc/mrc-client.example.ini index 589b8970018b46a6da2c2bfed9accacea9f6c9a3..8c0eda1dfe04d9434bb0445cd28317f45e1762ea 100644 --- a/xtrn/mrc/mrc-client.example.ini +++ b/xtrn/mrc/mrc-client.example.ini @@ -5,10 +5,15 @@ ping_interval = 60 [startup] room = lobby motd = true -banners = true +commands = +splash = true [aliases] -[client] -;change this to true if you want show the nick list on connect. -show_nicks=false +[theme] + +[msg_color] + +[show_nicks] + +[twit_list] diff --git a/xtrn/mrc/mrc-client.js b/xtrn/mrc/mrc-client.js index 2e0c7ee95926dacff9c27888d98e186e29485d76..4108746bb6ef0225554664d9d616516f07fc07be 100644 --- a/xtrn/mrc/mrc-client.js +++ b/xtrn/mrc/mrc-client.js @@ -5,6 +5,36 @@ * echicken -at- bbs.electronicchicken.com * * I started out with good intentions. At least it works. + * + * ------------------------------------------------------------- + * Codefenix's comments: + * codefenix -at- conchaos.synchro.net + * + * Sincere thanks and kudos to echicken for laying down the + * foundation with a great initial working build. I've used it + * for a long time to join weekly MRC meetups, and it has always + * worked great. + * + * In 2023 I took it upon myself to start studying this code, + * and attempted make some basic cosmetic changes I felt were + * needed, like timestamped messages and a togglable nick list. + * + * In 2024, nelgin implemented SSL (BIG thanks for that!). Later + * that same year I decided to get back into it add basic support + * for mentions, indented line-wrapping, and basic customizable + * themes. + * + * 2025 brings the most changes yet: twit filter, browseable + * mentions, and numerous fixes previously missed. + * + * I'm a big believer in MRC, and I've always tried to be + * respectful of the original design of this code and not make + * too many jarring changes. Please excuse some of the coding + * decisions I made that may appear less than elegant. I do hope + * to optimize and improve the features implemented. Thanks to + * everyone who has provided feedback and words of encouragement. + * + * */ load('sbbsdefs.js'); @@ -21,8 +51,11 @@ var input_state = 'chat'; var show_nicks = false; var stat_mode = 0; // 0 = Local Session stats (Chatters, Latency, & Mentions); 1 = Global MRC Stats +var mentions = []; +var mention_index = 0; const MENTION_MARKER = ascii(15); const INDENT_MARKER = ascii(28); +const NOTIF_FILE = system.data_dir + "msgs/" + format("%04d.msg", user.number); // default theme var theme_bg_color = BG_BLUE; @@ -38,13 +71,14 @@ const settings = { aliases: f.iniGetObject('aliases') || {}, client: f.iniGetObject('client') || {}, show_nicks: f.iniGetObject('show_nicks') || {}, - theme: f.iniGetObject('theme') || {} + theme: f.iniGetObject('theme') || {}, + msg_color: f.iniGetObject('msg_color') || {}, + twit_list: f.iniGetObject('twit_list') || {} }; f.close(); f = undefined; - const NICK_COLOURS = [ '\x01h\x01r', '\x01h\x01g', @@ -71,34 +105,6 @@ const ACT_LVL = [ /* 3: HI */ "\x01H\x01RHI" ]; -function pipe_to_ctrl_a(str) { // could use the pipeToCtrlA function in funclib.js instead - str = str.replace(/\|00/g, "\x01N\x01K"); - str = str.replace(/\|01/g, "\x01N\x01B"); - str = str.replace(/\|02/g, "\x01N\x01G"); - str = str.replace(/\|03/g, "\x01N\x01C"); - str = str.replace(/\|04/g, "\x01N\x01R"); - str = str.replace(/\|05/g, "\x01N\x01M"); - str = str.replace(/\|06/g, "\x01N\x01Y"); - str = str.replace(/\|07/g, "\x01N\x01W"); - str = str.replace(/\|08/g, "\x01H\x01K"); - str = str.replace(/\|09/g, "\x01H\x01B"); - str = str.replace(/\|10/g, "\x01H\x01G"); - str = str.replace(/\|11/g, "\x01H\x01C"); - str = str.replace(/\|12/g, "\x01H\x01R"); - str = str.replace(/\|13/g, "\x01H\x01M"); - str = str.replace(/\|14/g, "\x01H\x01Y"); - str = str.replace(/\|15/g, "\x01H\x01W"); - str = str.replace(/\|16/g, "\x01" + 0); - str = str.replace(/\|17/g, "\x01" + 4); - str = str.replace(/\|18/g, "\x01" + 2); - str = str.replace(/\|19/g, "\x01" + 6); - str = str.replace(/\|20/g, "\x01" + 1); - str = str.replace(/\|21/g, "\x01" + 5); - str = str.replace(/\|22/g, "\x01" + 3); - str = str.replace(/\|23/g, "\x01" + 7); - return str; -} - function resize_nicklist(frames, nicks) { const maxlen = show_nicks ? Math.max(1, nicks.reduce(function (a, c) { return c.length > a ? c.length : a; @@ -121,7 +127,7 @@ function redraw_nicklist(frames, nicks, colours) { } } -function init_display() { +function init_display(msg_color) { const w = console.screen_columns; const h = console.screen_rows; const f = { top: new Frame(1, 1, w, h, BG_BLACK|LIGHTGRAY) }; @@ -130,10 +136,12 @@ function init_display() { f.divider = new Frame(1, h - 1, w, 1, theme_bg_color|LIGHTGRAY, f.top); f.nicklist = new Frame(w - 2, 2, 2, h - 3, BG_BLACK|LIGHTGRAY, f.top); f.nicks = new Frame(w - 1, 2, 1, h - 3, BG_BLACK|LIGHTGRAY, f.nicklist); - f.input = new Frame(1, h, w, 1, BG_BLACK|WHITE, f.top); + f.input_color = new Frame(1, h, 1, 1, BG_BLACK|LIGHTGRAY, f.top); + f.input = new Frame(2, h, w-2, 1, BG_BLACK|WHITE, f.top); // TODO: Test f.output_scroll = new ScrollBar(f.output, { autohide: true }); f.nick_scroll = new ScrollBar(f.nicks, { autohide: true }); f.output.word_wrap = true; + f.input_color.putmsg( pipeToCtrlA( format( "|%02d>", msg_color) ) ); f.divider.gotoxy(f.divider.width - 5, 1); f.divider.putmsg(theme_fg_color + '/help'); f.top.open(); @@ -141,19 +149,19 @@ function init_display() { } function refresh_stats(frames, session) { - if (input_state == 'chat' ) { + if (input_state == 'chat' ) { frames.divider.clear(); if (stat_mode == 0) { - frames.divider.putmsg(format(theme_fg_color + "CLIENT STATS" + theme_2nd_color + "> " + + frames.divider.putmsg(format(theme_fg_color + "CLIENT STATS" + theme_2nd_color + "> " + theme_fg_color + "Latency" + theme_2nd_color + " [" + theme_fg_color + "%d" + theme_2nd_color + "] " + theme_fg_color + "Chatters" + theme_2nd_color + " [" + theme_fg_color + "%d" + theme_2nd_color + "] " + theme_fg_color + "Mentions" + theme_2nd_color + " [" + theme_fg_color + "%d" + theme_2nd_color + "]", session.latency, session.nicks.length, session.mention_count - )); + )); } else { - frames.divider.putmsg(format(theme_fg_color + "GLOBAL STATS" + theme_2nd_color + "> " + + frames.divider.putmsg(format(theme_fg_color + "GLOBAL STATS" + theme_2nd_color + "> " + theme_fg_color + "BBSes" + theme_2nd_color + " [" + theme_fg_color + "%d" + theme_2nd_color + "] " + theme_fg_color + "Rooms" + theme_2nd_color + " [" + theme_fg_color + "%d" + theme_2nd_color + "] " + theme_fg_color + "Users" + theme_2nd_color + " [" + theme_fg_color + "%d" + theme_2nd_color + "] " + @@ -179,8 +187,8 @@ function append_message(frames, msg, mention) { } // programmatically insert indent(s), if the message length exceeds the frame width. - if (strlen(msg) >= frames.output.width - 8) { - var indent = "\r\n" + (new Array(7).join( " " )) + INDENT_MARKER + " "; + if (strlen(msg) >= frames.output.width - (console.screen_columns < 132 ? 8 : 11)) { + var indent = "\r\n" + (new Array( console.screen_columns < 132 ? 7 : 10 ).join( " " )) + INDENT_MARKER + " "; msg = lfexpand(word_wrap(msg, frames.output.width - 9)).replace(/\r\n$/g, "").replace(/\r\n/g, indent).trim(); } @@ -190,20 +198,29 @@ function append_message(frames, msg, mention) { "\x01n" + msg + '\r\n' // message itself ); frames.output.scroll(0, -1); - if (input_state == 'scroll') frames.output.scrollTo(0, top); + + if (mention) { + mentions.push(top+1); + } + + if (input_state == 'scroll' || input_state == 'mentions') frames.output.scrollTo(0, top); } function getShortTime(d) { - return format( "%02d:%02d", d.getHours(), d.getMinutes() ); + if (console.screen_columns < 132 ) { + return format( "%02d:%02d", d.getHours(), d.getMinutes() ); + } else { + return format( "%02d:%02d:%02d", d.getHours(), d.getMinutes(), d.getSeconds() ); + } } -function display_message(frames, msg, mention) { //, colour param was listed but not used.. - const body = pipe_to_ctrl_a(truncsp(msg.body) || '').split(' '); +function display_message(frames, msg, mention) { + const body = pipeToCtrlA(truncsp(msg.body) || '').split(' '); append_message(frames, body[0] + '\x01n\x01w ' + body.slice(1).join(' ') + '\x01n\x01w', mention); } function display_server_message(frames, msg) { - append_message(frames, '\x01h\x01w' + pipe_to_ctrl_a(truncsp(msg) || '')); + append_message(frames, '\x01h\x01w' + pipeToCtrlA(truncsp(msg) || '')); } function display_title(frames, room, title) { @@ -225,9 +242,10 @@ function new_alias() { ) + suffix; set_alias(alias); settings.aliases[user.alias] = alias; + return alias; } -function list_themes () { +function list_themes() { var theme_list = []; var theme_files = directory(backslash(js.startup_dir) + "mrc-theme-*.ini"); for (var t = 0; t < theme_files.length; t++) { @@ -236,7 +254,7 @@ function list_themes () { return theme_list.join(", "); } -function set_theme (theme_name) { +function set_theme(theme_name) { var f = new File(js.startup_dir + format("mrc-theme-%s.ini", theme_name)); if ( f.open('r') ) { const theme = f.iniGetObject(); @@ -250,13 +268,99 @@ function set_theme (theme_name) { } } -function save_setting (alias, setting_name, setting_value) { +function save_setting(alias, setting_name, setting_value) { const f = new File(js.startup_dir + 'mrc-client.ini'); f.open('r+'); f.iniSetValue(setting_name, alias, setting_value); f.close(); } +function manage_twits(cmd, twit, twitlist, frames) { + switch (cmd) { + case "add": + if (twitlist.indexOf(twit) < 0) { + twitlist.push(twit); + if (twitlist.indexOf(twit) >= 0) { + save_setting(user.alias, "twit_list", twitlist.join(ascii(126))); + display_server_message(frames, "\x01n\x01c\x01h" + twit + "\x01w successfully added to Twit List\x01n.\x01n\x01w"); + } + } + break; + case "del": + var indexRmv = twitlist.indexOf(twit); + if (indexRmv >= 0) { + twitlist.splice(indexRmv, 1); + if (twitlist.indexOf(twit.toLowerCase()) < 0) { + save_setting(user.alias, "twit_list", twitlist.join(ascii(126))); + display_server_message(frames, "\x01n\x01c\x01h" + twit + "\x01w successfully REMOVED from Twit List\x01n.\x01n\x01w"); + } + } + break; + case "list": + if (twitlist.length > 0) { + display_server_message(frames, "\x01n\x01c\x01hTwit List (\x01w" + twitlist.length + "\x01c)\x01n:\x01n\x01w"); + for (var twit in twitlist ) { + display_server_message(frames, "\x01n\x01w - \x01h" + twitlist[twit] + "\x01n\x01w"); + } + } else { + display_server_message(frames, "\x01n\x01c\x01hYour Twit List is \x01wempty\x01n.\x01n\x01w"); + } + break; + case "clear": + twitlist.splice(0,twitlist.length); + save_setting(user.alias, "twit_list", ""); + display_server_message(frames, "\x01n\x01c\x01hYour Twit List is cleared.\x01n\x01w"); + break; + default: + display_server_message(frames, "\x01k\x01h*.:\x01n\x01w (*) Invalid twit command.\x01n\x01w"); + display_server_message(frames, "\x01k\x01h*.:\x01n\x01w See\x01H\x01K:\x01N \x01H/\x01Chelp\x01N \x01Htwit\x01n\x01w"); + display_server_message(frames, "\x01k\x01h*.: __\x01n\x01w"); + } +} + +function display_system_messages(frames) { // display local system messages (e.g.: user new email alerts, etc.) + if (file_size(NOTIF_FILE) > 0) { + var fnotif = new File(NOTIF_FILE); + if ( fnotif.open('r') ) { + const notif = fnotif.readAll(); + fnotif.close(); + fnotif = undefined; + display_server_message(frames, "\x01k\x01h*.:\x01y\x01h :: System Notification ::\x01n\x01w"); + for (var notif_line in notif) { + display_server_message(frames, "\x01k\x01h*.: " + notif[notif_line]); + } + display_server_message(frames, "\x01k\x01h*.:\x01k\x01h__\x01n\x01w"); + } + console.beep(); + file_removecase(NOTIF_FILE); + } +} + +function browse_mentions(frames) { + if (mentions.length > 0) { + mention_index = mentions.length-1; + input_state = 'mentions'; + frames.divider.clear(); + frames.divider.putmsg(theme_fg_color + format('UP/DOWN to browse mentions (%d of %d), ENTER to return', mention_index+1, mentions.length )); + frames.output.scrollTo(1, mentions[mention_index]); + frames.output_scroll.cycle(); + } +} + +function display_external_text(frames, which) { + var text = []; + var fhelp = new File(js.startup_dir + "mrc-" + which + ".msg"); + if ( fhelp.open('r') ) { + text = fhelp.readAll(); + fhelp.close(); + for (var line in text) { + display_server_message(frames, text[line]); + } + } else { + display_server_message(frames, "\x01w\x01h (*) Topic not found: \x01c" + which + "\x01n" ); + } +} + function main() { var msg; @@ -274,15 +378,21 @@ function main() { if (settings.show_nicks[user.alias]) { show_nicks = settings.show_nicks[user.alias]; } - if (settings.theme[user.alias]) { set_theme(settings.theme[user.alias]); } + if (settings.msg_color[user.alias]) { + session.msg_color = settings.msg_color[user.alias]; + } + if (settings.twit_list[user.alias]) { + session.twit_list = settings.twit_list[user.alias].split(ascii(126)); + } - const frames = init_display(); + const frames = init_display(session.msg_color); const inputline = new InputLine(frames.input); inputline.show_cursor = true; inputline.max_buffer = 140; + inputline.cursor_attr = BG_BLACK|session.msg_color; resize_nicklist(frames, []); redraw_nicklist(frames, []); @@ -297,42 +407,30 @@ function main() { session.on('error', function (err) { display_message(frames, { from_user: 'ERROR', body: err }); }); - session.on('help', function (cmd, help, ars) { - if (!ars || user.compare_ars(ars)) { - display_server_message(frames, format('\x01h\x01w/\x01h\x01c%s \x01h\x01w- \x01n\x01w%s', cmd, help)); + + session.on('local_help', function (help_topic) { + display_external_text(frames, "help-" + (help_topic ? help_topic : "main")); + if (help_topic == "theme") { + display_server_message(frames, "\x01n\x01w Available theme options: " + list_themes()); } - }); - session.on('local_help', function (msg) { - display_server_message(frames, '\x01h\x01w/\x01h\x01cscroll \x01h\x01w- \x01n\x01wScroll the output area'); - display_server_message(frames, '\x01h\x01w/\x01h\x01cscroll_nicks \x01h\x01w- \x01n\x01wScroll the nicklist'); - display_server_message(frames, '\x01h\x01w/\x01h\x01cnick_prefix \x01h\x01w- \x01n\x01wSet a single-character prefix for your handle, eg. /nick_prefix @'); - display_server_message( - frames, - '\x01h\x01w/\x01h\x01cnick_color \x01h\x01w- \x01n\x01wSet your nick color to one of ' - + PIPE_COLOURS.reduce(function (a, c) { - a += format('|%02d%s ', c, c); - return a; - }, '') - + ', eg. /nick_color 11' - ); - display_server_message(frames, '\x01h\x01w/\x01h\x01cnick_suffix \x01h\x01w- \x01n\x01wSet an eight-character suffix for your handle, eg. /nick_suffix <poop>'); - display_server_message(frames, '\x01h\x01w/\x01h\x01ctoggle_nicks \x01h\x01w- \x01n\x01wShow/hide the nicklist'); - display_server_message(frames, '\x01h\x01w/\x01h\x01ctoggle_stats \x01h\x01w- \x01n\x01wSwitch between global MRC stats and client/session stats.'); - display_server_message(frames, "\x01h\x01w/\x01h\x01ctheme \x01n\x01w- \x01nSelect a theme: " + list_themes() ); - display_server_message(frames, '\x01h\x01w/\x01h\x01cquit \x01n\x01w- \x01h\x01wExit the program'); + display_server_message(frames, "\x01k\x01h*.:\x01k\x01h__\x01n\x01w"); }); session.on('message', function (msg) { if (msg.from_user == 'SERVER') { display_server_message(frames, msg.body); } else { - var mention = false; - if (msg.body.toUpperCase().indexOf(user.alias.toUpperCase()) >= 0 && msg.from_user !== user.alias) { - console.beep(); - mention = true; - session.mention_count = session.mention_count + 1; - refresh_stats (frames, session); - } - display_message(frames, msg, mention); //, nick_colours[msg.from_user] || ''); + if (session.twit_list.indexOf(msg.from_user.toLowerCase()) < 0) { + var mention = false; + if (msg.body.toUpperCase().indexOf(user.alias.toUpperCase()) >= 0 && msg.from_user !== user.alias) { + console.beep(); + mention = true; + session.mention_count = session.mention_count + 1; + refresh_stats (frames, session); + } + display_message(frames, msg, mention); + } /*else { + log ( "filtered message from " + msg.from_user + "."); + }*/ } }); session.on('nicks', function (nicks) { @@ -341,7 +439,7 @@ function main() { }); resize_nicklist(frames, nicks); redraw_nicklist(frames, nicks, nick_colours); - refresh_stats (frames, session); + refresh_stats(frames, session); }); session.on('sent_privmsg', function (user, msg) { display_message(frames, { body: '--> ' + (nick_colours[user] || '') + user + ' ' + msg }); @@ -356,19 +454,64 @@ function main() { refresh_stats(frames, session); }); + if (settings.startup.splash) display_external_text(frames, "splash"); if (settings.startup.motd) session.motd(); - if (settings.startup.banners) session.banners(); + session.send_notme("|07- |11" + user.alias + " |03has arrived."); + mswait(20); + session.send_command('TERMSIZE:' + console.screen_columns + 'x' + console.screen_rows); + mswait(20); + session.send_command('BBSMETA: SecLevel(' + user.security.level + ') SysOp(' + system.operator + ')'); + mswait(20); + session.send_command('USERIP:' + (bbs.atcode("IP") == "127.0.0.1" ? client.ip_address : bbs.atcode("IP")) ); if (settings.startup.room) session.join(settings.startup.room); + if (settings.startup.commands) { + var startup_cmds = settings.startup.commands.split(','); + for (var startup_cmd in startup_cmds) { + session.send_command(startup_cmds[startup_cmd].toUpperCase()); + } + } + var cmd, line, user_input; while (!js.terminated && !break_loop) { session.cycle(); - frames.divider.gotoxy(frames.divider.width - 16, 1); - frames.divider.putmsg(theme_2nd_color + "[" + theme_fg_color + ("000" + inputline.buffer.length).slice(-3) + theme_2nd_color + '/' + theme_fg_color + inputline.max_buffer + theme_2nd_color + "]"); + if (input_state == 'chat') { + frames.divider.gotoxy(frames.divider.width - 16, 1); + frames.divider.putmsg(theme_2nd_color + "[" + theme_fg_color + ("000" + inputline.buffer.length).slice(-3) + theme_2nd_color + '/' + theme_fg_color + inputline.max_buffer + theme_2nd_color + "]"); + + if (inputline.buffer.search(/^\/(identify|register|roompass|update password|roomconfig password) ./i) == 0) { + inputline.frame.left(); + inputline.frame.write("*"); // mask text after commands involving passwords + } + } user_input = inputline.getkey(); if (typeof user_input == 'string') { if (input_state == 'chat') { - if (user_input.substring(0, 1) == '/') { // It's a command + // Define some hotkeys + if (user_input == KEY_LEFT || user_input == KEY_RIGHT) { // Change user's text color + session.msg_color = session.msg_color + (user_input == KEY_LEFT ? -1 : 1); + if (session.msg_color < 1) { + session.msg_color = 15; + } else if (session.msg_color > 15) { + session.msg_color = 1; + } + inputline.cursor_attr = BG_BLACK|session.msg_color; + frames.input_color.clear(); + frames.input_color.putmsg( pipeToCtrlA( format( "|%02d>", session.msg_color) ) ); + save_setting(user.alias, "msg_color", session.msg_color); + } else if (user_input == KEY_PAGEUP) { // Shortcut for scroll + input_state = 'scroll'; + if (frames.output.offset.y > 0) { + frames.output.scroll(0, -1 * frames.output.height); + frames.output_scroll.cycle(); + } + frames.divider.clear(); + frames.divider.putmsg(theme_fg_color + 'UP/DOWN, PGUP/PGDN, HOME/END to scroll, ENTER to return'); + } else if (user_input == KEY_UP) { + browse_mentions(frames); + } else if (user_input == CTRL_D) { + inputline.clear(); + } else if (user_input.substring(0, 1) == '/') { // It's a command cmd = user_input.split(' '); cmd[0] = cmd[0].substr(1).toLowerCase(); switch (cmd[0]) { @@ -435,20 +578,38 @@ function main() { redraw_nicklist(frames, session.nicks, nick_colours); save_setting(user.alias, "show_nicks", show_nicks); break; - case "toggle_stats": + case "toggle_stats": stat_mode = stat_mode === 0 ? 1 : 0; refresh_stats(frames, session); break; case "theme": - set_theme( cmd[1] ); - frames.title.attr = theme_bg_color|theme_fg_color; - frames.divider.attr = theme_bg_color|theme_fg_color; - display_title(frames, session.room, session.room_topic); - refresh_stats(frames, session); + if (cmd.length == 2) { + set_theme( cmd[1] ); + frames.title.attr = theme_bg_color|theme_fg_color; + frames.divider.attr = theme_bg_color|theme_fg_color; + display_title(frames, session.room, session.room_topic); + refresh_stats(frames, session); + } + break; + case "mentions": + browse_mentions(frames); + break; + case "twit": + if (cmd.length >= 2) { + manage_twits( cmd[1].toLowerCase(), cmd.slice(2).join(' ').toLowerCase(), session.twit_list, frames ); + } break; default: if (typeof session[cmd[0]] == 'function') { session[cmd[0]](cmd.slice(1).join(' ')); + // This check doesn't let commands out unless they're + // strictly defined in mrc-session.js, meaning we'd have + // to constantly chase down new server commands each time + // they're added/removed. + } else { + // Just send the command to the server if it's not defined as + // a local command. The server will tell the user if it's not valid. + session.send_command(user_input.substr(1)); } break; } @@ -475,10 +636,32 @@ function main() { } else if ( // Regular message typeof user_input == 'string' // Could be bool at this point && user_input != '' - && [KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT].indexOf(user_input) < 0 + && [KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, + KEY_HOME, KEY_END, KEY_INSERT, KEY_PAGEDN].indexOf(user_input) < 0 ) { session.send_room_message(user_input); } + } else if (input_state == 'mentions') { + if (user_input == KEY_UP || user_input == KEY_DOWN) { + mention_index = mention_index + (user_input == KEY_UP ? -1 : 1); + if (mention_index < 0) { + mention_index = 0; + } else if (mention_index > mentions.length-1) { + mention_index = mentions.length-1; + } + if (frames.output.offset.y !== mentions[mention_index] ) { + frames.output.scrollTo(1, mentions[mention_index]); + frames.output_scroll.cycle(); + frames.divider.clear(); + frames.divider.putmsg(theme_fg_color + format('UP/DOWN to browse mentions (%d of %d), ENTER to return', mention_index+1, mentions.length )); + } + } else if (user_input == '' || user_input == 'q') { + frames.output.scrollTo(1, frames.output.data_height - frames.output.height-1); + frames.output_scroll.cycle(); + input_state = 'chat'; + session.mention_count = 0; + refresh_stats(frames, session); + } } else if (input_state == 'scroll' || input_state == 'scroll_nicks') { var sframe = input_state == 'scroll' ? frames.output : frames.nicks; if (user_input == KEY_UP && sframe.offset.y > 0) { @@ -510,8 +693,12 @@ function main() { frames.nick_scroll.cycle(); console.gotoxy(inputline.frame.__relations__.child[0].x, inputline.frame.y); } + + display_system_messages(frames); + yield(); } + console.clear(autopause=false); // prevent an unintended auto-pause when quitting } main(); diff --git a/xtrn/mrc/mrc-connector.js b/xtrn/mrc/mrc-connector.js index 3de85ea00ceeed085c7fe062533874ebcf6a84a0..380c1cbf54735b39dc1095ed18d330413a23dcec 100644 --- a/xtrn/mrc/mrc-connector.js +++ b/xtrn/mrc/mrc-connector.js @@ -28,8 +28,8 @@ f = undefined; if (!settings.ssl) settings.ssl=false; -const PROTOCOL_VERSION = '1.3.1'; -const MAX_LINE = 256; +const PROTOCOL_VERSION = '1.3.2'; +const MAX_LINE = 512; const FROM_SITE = system.name.replace(/ /g, "_"); const SYSTEM_NAME = system_info.system_name || system.name; @@ -221,8 +221,10 @@ function mrc_receive(sock) { line = sock.recvline(MAX_LINE, settings.timeout); if (!line || line == '') break; latency_tracker.forEach(function(m) { - if (m.line===line.trim()) { + if (m.line===line.trim() || + (m.line.indexOf("~STATS") >= 0 && line.indexOf("~STATS") >= 0 ) ) { client_send({ from_user: "SERVER", to_user: 'CLIENT', body: 'LATENCY:' + (Date.now() - m.time) }); + log(LOG_DEBUG, 'Latency: ' + (Date.now() - m.time) ); latency_tracker = []; } }); @@ -239,14 +241,12 @@ function mrc_receive(sock) { fMa.write(message.body.substr(message.body.indexOf(':')+1).trim()); fMa.close(); } - if (['', 'ALL', FROM_SITE].indexOf(message.to_site) > -1) { - if (['', 'CLIENT', 'ALL', 'NOTME'].indexOf(message.to_user) > -1) { - // Forward to all clients - client_send(message); - } else { - // Send to this user - client_send(message, message.to_user); - } + if (['', 'CLIENT', 'ALL', 'NOTME'].indexOf(message.to_user) > -1) { + // Forward to all clients + client_send(message); + } else { + // Send to this user + client_send(message, message.to_user); } yield(); } @@ -274,6 +274,7 @@ function main() { Object.keys(clients).forEach(function (e, i) { if (!clients[e].socket.is_connected) { + mrc_send(mrc_sock, clients[e].username, "", "NOTME", "", "", "|07- |12" + clients[e].username + " |04has left chat|08."); mrc_send(mrc_sock, clients[e].username, '', 'SERVER', '', '', 'LOGOFF'); client_send({ from_user: clients[e].username, to_user: 'SERVER', body: 'LOGOFF' }); // Notify local clients delete clients[e]; diff --git a/xtrn/mrc/mrc-help-main.msg b/xtrn/mrc/mrc-help-main.msg new file mode 100644 index 0000000000000000000000000000000000000000..2b82190c7bb9f130bca968c50d0d3d41a6fc1faa --- /dev/null +++ b/xtrn/mrc/mrc-help-main.msg @@ -0,0 +1,21 @@ +NB__[ HWList of available commandsN: B]_____________________________ +WH/CinfoN�HK:N View information about a BBSHK:N H/CinfoN H# +WH/Ct Kor W/Cmsg�K:N Send a direct messageHK:N�H/CtN HnickN HBmessage +WH/CrN�HK:N Reply to last direct messageHK:N H/CrB message +WH/CjoinN�HK:N Move to a new roomHK:W�/CjoinN Hroom_name +WH/CtopicN�HK:N Change room topic:�H/CtopicW topic +WH/CroomsN�HK:N List available rooms +WH/CusersN�HK:N List users +WH/CwhoonN�HK:N List users and BBSes +WH/CmotdN�HK:N Display the Message of the Day +WH/Cscroll Kor CPGUPK:N Scroll the chat window +WH/Cmentions Kor CUPK:N Reset mention counter and review mentions +WH/Cscroll_nicksN HK:N Scroll the nicklist +WH/Ctoggle_nicksN HK:N Show/hide the nicklist +WH/Ctoggle_statsN HK:N Toggle MRC stats view +WH/CthemeN�HK:N Select a theme HK..........N seeHK:N H/ChelpN Htheme +WH/CtwitN�HK:N Twit List management HK....N seeHK:N H/ChelpN Htwit +WH/CquitN�HK:N Exit the program +WHCLEFTK/CRIGHTW�K:N Change your text color HK..N seeHK:N H/ChelpN Hnick +WHCCTRLK+CDN�HK:N Clear the input line +NFor a list of additional HSERVERN commands, see H/CquoteN Hhelp \ No newline at end of file diff --git a/xtrn/mrc/mrc-help-nick.msg b/xtrn/mrc/mrc-help-nick.msg new file mode 100644 index 0000000000000000000000000000000000000000..b0941fcadb05fce51c858bc33542e70874d8e36e --- /dev/null +++ b/xtrn/mrc/mrc-help-nick.msg @@ -0,0 +1,12 @@ +NB__[ HWNick customization optionsN: B]_____________________________ +HW/Cnick_prefix K:W NSet a single-character prefix for your handle +N�eg.H /Cnick_prefixN H@ +WH/Cnick_colorN HK:W NSet your nick color +H�NG02HW NC03HW NR04HW NM05HW NY06HW B09W G10W C11W R12M 13W Y14W 15 +WH/Cnick_suffix K:W NSet an eight-character suffix for your handle +N�eg.H N/HCnick_suffix W<afilz> + +N You can include pipe color codes in your nick_prefix and +N nick_suffix. Get as creative as you want! + +HB NExample: HB@CUserNameB<GaFiLZB> diff --git a/xtrn/mrc/mrc-help-theme.msg b/xtrn/mrc/mrc-help-theme.msg new file mode 100644 index 0000000000000000000000000000000000000000..77bbc7042b3bb13e9544737738092789794387e3 --- /dev/null +++ b/xtrn/mrc/mrc-help-theme.msg @@ -0,0 +1,5 @@ +NB__[ HWHow to set a custom themeN: B]______________________________ + +N Usage: + WH/Ctheme NHnameoftheme +N \ No newline at end of file diff --git a/xtrn/mrc/mrc-help-twit.msg b/xtrn/mrc/mrc-help-twit.msg new file mode 100644 index 0000000000000000000000000000000000000000..b2ba22b3731e7a7c5b1c97bc6bd7e3d262812aea --- /dev/null +++ b/xtrn/mrc/mrc-help-twit.msg @@ -0,0 +1,10 @@ +NB__[ HWHow to use the twit listN: B]_______________________________ + +N Basic twit list implemented! Hides messages from problematic +N chatters. + +N Usage: + WH/Ctwit NHadd HBnameoftwit K:W NFilters out nameoftwit's messages + WH/Ctwit NHdel HBnameoftwit K:W Nnameoftwit's messages are re-allowed + WH/Ctwit NHlist K:W NLists twits the user has added + WH/Ctwit NHclear K:W NEmpty's the user's twit list diff --git a/xtrn/mrc/mrc-session.js b/xtrn/mrc/mrc-session.js index 6699434858d261824231a6f654dfc6d1dd40f6f0..ca6e5e351681fd014a2bcfd317885d7ca26ff07d 100644 --- a/xtrn/mrc/mrc-session.js +++ b/xtrn/mrc/mrc-session.js @@ -16,7 +16,10 @@ function MRC_Session(host, port, user, pass, alias) { alias: alias || user, stats: ['-','-','-','0'], mention_count: 0, - latency: '-' + latency: '-', + msg_color: 7, + twit_list: [], + last_private_msg_from: "" }; const callbacks = { @@ -41,10 +44,10 @@ function MRC_Session(host, port, user, pass, alias) { function send_message(to_user, to_room, body) { if (body.length + state.alias.length + 1 > 250) { word_wrap(body, 250 - 1 - state.alias.length).split(/\n/).forEach(function (e) { - send(to_user, '', to_room, state.alias + ' ' + e); + send(to_user, '', to_room, state.alias + ' ' + format("|%02d", state.msg_color) + e); }); } else { - send(to_user, '', to_room, state.alias + ' ' + body); + send(to_user, '', to_room, state.alias + ' ' + format("|%02d", state.msg_color) + body); } } @@ -80,7 +83,7 @@ function MRC_Session(host, port, user, pass, alias) { break; case 'STATS': state.stats = params.split(' '); - emit('stats'); //, state.stats); + emit('stats'); break; case 'LATENCY': state.latency = params; @@ -90,6 +93,10 @@ function MRC_Session(host, port, user, pass, alias) { emit('message', msg); break; } + if (msg.body.search(/just joined room/) > -1 || + msg.body.search(/session has timed-out/) > -1) { + send_command('USERLIST', 'ALL'); + } } else if (msg.to_user == 'SERVER') { if (msg.body == 'LOGOFF') { uidx = state.nicks.indexOf(msg.from_user); @@ -97,8 +104,13 @@ function MRC_Session(host, port, user, pass, alias) { state.nicks.splice(uidx, 1); emit('nicks', state.nicks); } - } + } } else if (msg.to_user == '' || user.toLowerCase() == msg.to_user.toLowerCase()) { + + if (user.toLowerCase() == msg.to_user.toLowerCase()) { + state.last_private_msg_from = msg.from_user; + } + if (msg.to_room == '' || msg.to_room == state.room) { emit('message', msg); } @@ -112,9 +124,9 @@ function MRC_Session(host, port, user, pass, alias) { state.nicks.splice(uidx, 1); emit('nicks', state.nicks); } - } else if (msg.body.search(/just joined room/) > -1) { - send_command('USERLIST', 'ALL'); - } + } /*else if (msg.body.search(/just joined room/) > -1) { + send_command('USERLIST', 'ALL'); // moved above to msg.from_user == 'SERVER' + }*/ emit('message', msg); } } @@ -122,6 +134,10 @@ function MRC_Session(host, port, user, pass, alias) { this.send_room_message = function (msg) { send_message('', state.room, msg); } + + this.send_notme = function (msg) { + send("NOTME", "", "", msg); + } this.send_private_messsage = function (user, msg) { msg = '|11(|03Private|11)|07 ' + msg; @@ -140,7 +156,11 @@ function MRC_Session(host, port, user, pass, alias) { password: pass, alias: state.alias }) + '\r\n'); + + this.send_command('IAMHERE'); + mswait(20); this.send_command('USERLIST', 'ALL'); + mswait(20); this.send_command('STATS', 'ALL'); } @@ -181,50 +201,53 @@ function MRC_Session(host, port, user, pass, alias) { } const commands = { - banners: { - help: 'List of banners from server' // Doesn't do anything? - }, - chatters: { - help: 'List current users' - }, - bbses: { - help: 'List connected BBSs', - command: 'CONNECTED' - }, + + // This section has been cleaned up significantly. + // + // Server commands no longer need to be strictly defined + // here. + // + // Some commands are still defined if they have specific + // function calls attached to them in mrc-client.js + // (e.g. /motd) and or are local command shortcuts + // (e.g. /t and /r). + // + // Any command not defined here (or in mrc-client.js) will + // be assumed to be a server command. The server will + // inform the user if a command is invalid. + // + // The "help" properties are no longer needed, since + // help is contained in external .msg file. They have been + // left commended out to provide reference. + // + help: { - help: 'Display this help message', + //help: 'Display this help message', callback: function (str) { - emit('help', 'List of available commands:', ''); - for (var c in commands) { - emit('help', c, commands[c].help, commands[c].ars); - } - emit('local_help'); + emit('local_help', str); } }, info: { - help: 'View information about a BBS (/info #)', + //help: 'View information about a BBS (/info #)', callback: function (str) { this.send_command('INFO ' + str); } }, - join: { - help: 'Move to a new room: /join room_name', + join: { + //help: 'Move to a new room: /join room_name', callback: function (str) { // validate valid room name? - str = str.replace(/^#/, ''); + str = str.replace(/^#/, ''); this.send_command(format('NEWROOM:%s:%s', state.room, str)); state.room = str; state.nicks = []; this.send_command('USERLIST', 'ALL'); } }, - meetups: { - help: 'Display information about upcoming meetups' - }, motd: { - help: 'Display the Message of the Day' + //help: 'Display the Message of the Day' }, - msg: { - help: 'Send a private message: /msg nick message goes here', + msg: { // This is largely overtaken by /t, but we'll still handle it. + //help: 'Send a private message: /msg nick message goes here', callback: function (str) { const cmd = str.split(' '); if (cmd.length > 1 && state.nicks.indexOf(cmd[0]) > -1) { @@ -232,8 +255,8 @@ function MRC_Session(host, port, user, pass, alias) { } } }, - t: { // added as shorthand for /msg - help: 'Shorthand for /msg: /t nick message goes here', + t: { + //help: 'Send a private message: /t nick message goes here', callback: function (str) { const cmd = str.split(' '); if (cmd.length > 1 && state.nicks.indexOf(cmd[0]) > -1) { @@ -241,70 +264,41 @@ function MRC_Session(host, port, user, pass, alias) { } } }, + r: { + //help: 'Reply to last private message: /r message goes here', + callback: function (str) { + if (state.last_private_msg_from) { + this.send_private_messsage(state.last_private_msg_from, str); + } + } + }, quote: { - help: 'Send a raw command to the server', + //help: 'Send a raw command to the server', callback: function (str) { this.send_command(str); } }, quit: { - help: 'Quit the program', + //help: 'Quit the program', callback: function () { - handle.close(); emit('disconnect'); + handle.close(); } }, rooms: { - help: 'List available rooms', + //help: 'List available rooms', command: 'LIST' }, stats: { - help: 'Return anonymous server stats', + //help: 'Return anonymous server stats', command: 'statistics' }, topic: { - help: 'Change the topic of the current room', + //help: 'Change the topic of the current room', callback: function (str) { this.send_command(format('NEWTOPIC:%s:%s', state.room, str)); } - }, - users: { - help: 'Display list of users' - }, - whoon: { - help: 'Display list of users and BBSs' - }, - afk: { - help: "Set yourself AFK (Shortcut for STATUS AFK)", - callback: function (str) { - this.send_command('AFK ' + str); - } - }, - register: { - help: "Register handle on server (MRC Trust)", - callback: function (str) { - this.send_command('REGISTER ' + str); - } - }, - identify: { - help: "Identify as a registered user (MRC Trust)", - callback: function (str) { - this.send_command('IDENTIFY ' + str); - } - }, - update: { - help: "Update user registration (MRC Trust)", - callback: function (str) { - this.send_command('UPDATE ' + str); - } - }, - trust: { - help: "MRC Trust Info (MRC Trust)", - callback: function (str) { - this.send_command('TRUST ' + str); - } - } - + } }; Object.keys(commands).forEach(function (e) { @@ -312,11 +306,11 @@ function MRC_Session(host, port, user, pass, alias) { this[e] = commands[e].callback; } else if (commands[e].command) { this[e] = function () { - this.send_command(commands[e].command.toUpperCase()); + this.send_command(commands[e].command); } } else { this[e] = function () { - this.send_command(e.toUpperCase()); + this.send_command(e); } } }, this); diff --git a/xtrn/mrc/mrc-splash.msg b/xtrn/mrc/mrc-splash.msg new file mode 100644 index 0000000000000000000000000000000000000000..a47724bd3ea8c009dd0ff6a9f65b6251a1648f7a --- /dev/null +++ b/xtrn/mrc/mrc-splash.msg @@ -0,0 +1,8 @@ +N�HK.N HK.N . HK~N HK~N HK~N HK.N HK~N HK~N HK.N HK~N HK~N HK~N HK~N HK.N HK~N HK~N HK~N . HK.N HK. +N HK.N HK.N . H.N HB88888b.d88b.N HB888d888 .d8888b W.N .HK .N HK. + .N .HK W.K NB.W B8H88N HB"888N HB"88b NB8H88P"NB dH88P"N�B.HK W.N . HK. + N.HK W.K NB.W B.W B8H88N B8H88 NB8H88 NB8H88N�B8H88NB�.W B.HK W.N . +HK . N.HK W.K NB.W B8H88N B8H88 NB8H88 NB8H88N�BY8H8b.N�B.HK W.N . HK. + . .N . H.N B8H88N B8H88 NB8H88 NB8H88N�B"Y8H888 W.N . HK.N HK. + ` .N HK.N HK=N==H=========================N==HK=N HK.N HK. ' +N�HK-=[N HCMNCulti HRNCelay HCNChat forW HCSynchronetN HK]=- diff --git a/xtrn/mrc/readme.txt b/xtrn/mrc/readme.txt index 4fb29996f63965c8e915f200e571ae917ebc9a88..8f8482e6699069235acd3312d5bc1063d8f1466f 100644 --- a/xtrn/mrc/readme.txt +++ b/xtrn/mrc/readme.txt @@ -60,7 +60,6 @@ colour codes are permitted: http://wiki.mysticbbs.com/doku.php?id=displaycodes#color_codes_pipe_colors -I may write a CTRL-A to pipe converter at some later date. mrc-connector.ini: @@ -77,11 +76,16 @@ mrc-client.ini: above instructions while editing '/sbbs/ctrl/services.ini'. - The 'ping_interval' setting should be left at the default value unless you have a good reason for changing it. - - The values in the [startup] section determine which room the client joins - on startup, and whether the Message of the Day and banners are displayed. - - Change show_nicks in the [client] section to always display the nick list - when connecting to the MRC server. - + - The values in the [startup] section control the following: + room: Which room the client joins on startup. + motd: Whether the Message of the Day is displayed. + commands: Any additional commands to execute, eg: "chatters" to display + the list of active users in the room on startup. Use commas + to separate multiple commands. + splash: Whether the "Multi Relay Chat for Synchronet" splash is shown + on startup. + - The [aliases], [theme], [msg_color], [show_nicks], and [twit_list] sections + are for user settings which get added or updated any time a user uses them. 4) MRC Stats @@ -123,19 +127,35 @@ see "Yes" next to your BBS in the SSL column. 6) Themes MRC comes with several customizable theme files. These can be added/edited as -the system wishes. The client automatically detects any available theme files +the sysop wishes. The client automatically detects any available theme files matching the pattern: mrc-theme-<theme_name>.ini -Available themes are listed in /help. User selects a theme by typing +Available themes are listed in /help theme. User selects a theme by typing /theme <name>, and the selected theme gets saved to settings for future sessions. If a theme file is not available, the classic blue theme gets used by default. +To customize the splash that gets shown on startup, edit the mrc-splash.msg +file. Or just disable it by setting splash = false in mrc-client.ini -7) Support + +7) Known issues + +- Outgoing messages beginning with a semicolon do not get sent out to the MRC + server. For example, a winking face emoticon: ;) or ;-) + + This is because the ; character is used by the inputline.js library to + indicate the start of a sysop command. In the case of the MRC client, such + text is simply ignored and does not get sent over. + + As a workround, you may simply insert a space in front of the message you + want to send that starts with a semicolon. + + +8) Support - Post a message to 'echicken' in the Synchronet Sysops area on DOVE-Net - Find me on irc.synchro.net in #synchronet