Skip to content
Snippets Groups Projects
Commit 84278e60 authored by Rob Swindell's avatar Rob Swindell :speech_balloon:
Browse files

Merge branch 'massive-mrc-update-20250303' into 'master'

MRC Change List on 3/3/2025:

See merge request !517
parents f08ee496 cb30ad1a
Branches
No related tags found
1 merge request!517MRC Change List on 3/3/2025:
...@@ -5,10 +5,15 @@ ping_interval = 60 ...@@ -5,10 +5,15 @@ ping_interval = 60
[startup] [startup]
room = lobby room = lobby
motd = true motd = true
banners = true commands =
splash = true
[aliases] [aliases]
[client] [theme]
;change this to true if you want show the nick list on connect.
show_nicks=false [msg_color]
[show_nicks]
[twit_list]
...@@ -5,6 +5,36 @@ ...@@ -5,6 +5,36 @@
* echicken -at- bbs.electronicchicken.com * echicken -at- bbs.electronicchicken.com
* *
* I started out with good intentions. At least it works. * 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'); load('sbbsdefs.js');
...@@ -21,8 +51,11 @@ var input_state = 'chat'; ...@@ -21,8 +51,11 @@ var input_state = 'chat';
var show_nicks = false; var show_nicks = false;
var stat_mode = 0; // 0 = Local Session stats (Chatters, Latency, & Mentions); 1 = Global MRC Stats 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 MENTION_MARKER = ascii(15);
const INDENT_MARKER = ascii(28); const INDENT_MARKER = ascii(28);
const NOTIF_FILE = system.data_dir + "msgs/" + format("%04d.msg", user.number);
// default theme // default theme
var theme_bg_color = BG_BLUE; var theme_bg_color = BG_BLUE;
...@@ -38,13 +71,14 @@ const settings = { ...@@ -38,13 +71,14 @@ const settings = {
aliases: f.iniGetObject('aliases') || {}, aliases: f.iniGetObject('aliases') || {},
client: f.iniGetObject('client') || {}, client: f.iniGetObject('client') || {},
show_nicks: f.iniGetObject('show_nicks') || {}, 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.close();
f = undefined; f = undefined;
const NICK_COLOURS = [ const NICK_COLOURS = [
'\x01h\x01r', '\x01h\x01r',
'\x01h\x01g', '\x01h\x01g',
...@@ -71,34 +105,6 @@ const ACT_LVL = [ ...@@ -71,34 +105,6 @@ const ACT_LVL = [
/* 3: HI */ "\x01H\x01RHI" /* 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) { function resize_nicklist(frames, nicks) {
const maxlen = show_nicks ? Math.max(1, nicks.reduce(function (a, c) { const maxlen = show_nicks ? Math.max(1, nicks.reduce(function (a, c) {
return c.length > a ? c.length : a; return c.length > a ? c.length : a;
...@@ -121,7 +127,7 @@ function redraw_nicklist(frames, nicks, colours) { ...@@ -121,7 +127,7 @@ function redraw_nicklist(frames, nicks, colours) {
} }
} }
function init_display() { function init_display(msg_color) {
const w = console.screen_columns; const w = console.screen_columns;
const h = console.screen_rows; const h = console.screen_rows;
const f = { top: new Frame(1, 1, w, h, BG_BLACK|LIGHTGRAY) }; const f = { top: new Frame(1, 1, w, h, BG_BLACK|LIGHTGRAY) };
...@@ -130,10 +136,12 @@ function init_display() { ...@@ -130,10 +136,12 @@ function init_display() {
f.divider = new Frame(1, h - 1, w, 1, theme_bg_color|LIGHTGRAY, f.top); 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.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.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.output_scroll = new ScrollBar(f.output, { autohide: true });
f.nick_scroll = new ScrollBar(f.nicks, { autohide: true }); f.nick_scroll = new ScrollBar(f.nicks, { autohide: true });
f.output.word_wrap = 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.gotoxy(f.divider.width - 5, 1);
f.divider.putmsg(theme_fg_color + '/help'); f.divider.putmsg(theme_fg_color + '/help');
f.top.open(); f.top.open();
...@@ -179,8 +187,8 @@ function append_message(frames, msg, mention) { ...@@ -179,8 +187,8 @@ function append_message(frames, msg, mention) {
} }
// programmatically insert indent(s), if the message length exceeds the frame width. // programmatically insert indent(s), if the message length exceeds the frame width.
if (strlen(msg) >= frames.output.width - 8) { if (strlen(msg) >= frames.output.width - (console.screen_columns < 132 ? 8 : 11)) {
var indent = "\r\n" + (new Array(7).join( " " )) + INDENT_MARKER + " "; 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(); 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) { ...@@ -190,20 +198,29 @@ function append_message(frames, msg, mention) {
"\x01n" + msg + '\r\n' // message itself "\x01n" + msg + '\r\n' // message itself
); );
frames.output.scroll(0, -1); 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) { function getShortTime(d) {
if (console.screen_columns < 132 ) {
return format( "%02d:%02d", d.getHours(), d.getMinutes() ); 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.. function display_message(frames, msg, mention) {
const body = pipe_to_ctrl_a(truncsp(msg.body) || '').split(' '); const body = pipeToCtrlA(truncsp(msg.body) || '').split(' ');
append_message(frames, body[0] + '\x01n\x01w ' + body.slice(1).join(' ') + '\x01n\x01w', mention); append_message(frames, body[0] + '\x01n\x01w ' + body.slice(1).join(' ') + '\x01n\x01w', mention);
} }
function display_server_message(frames, msg) { 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) { function display_title(frames, room, title) {
...@@ -225,6 +242,7 @@ function new_alias() { ...@@ -225,6 +242,7 @@ function new_alias() {
) + suffix; ) + suffix;
set_alias(alias); set_alias(alias);
settings.aliases[user.alias] = alias; settings.aliases[user.alias] = alias;
return alias;
} }
function list_themes() { function list_themes() {
...@@ -257,6 +275,92 @@ function save_setting (alias, setting_name, setting_value) { ...@@ -257,6 +275,92 @@ function save_setting (alias, setting_name, setting_value) {
f.close(); 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() { function main() {
var msg; var msg;
...@@ -274,15 +378,21 @@ function main() { ...@@ -274,15 +378,21 @@ function main() {
if (settings.show_nicks[user.alias]) { if (settings.show_nicks[user.alias]) {
show_nicks = settings.show_nicks[user.alias]; show_nicks = settings.show_nicks[user.alias];
} }
if (settings.theme[user.alias]) { if (settings.theme[user.alias]) {
set_theme(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); const inputline = new InputLine(frames.input);
inputline.show_cursor = true; inputline.show_cursor = true;
inputline.max_buffer = 140; inputline.max_buffer = 140;
inputline.cursor_attr = BG_BLACK|session.msg_color;
resize_nicklist(frames, []); resize_nicklist(frames, []);
redraw_nicklist(frames, []); redraw_nicklist(frames, []);
...@@ -297,34 +407,19 @@ function main() { ...@@ -297,34 +407,19 @@ function main() {
session.on('error', function (err) { session.on('error', function (err) {
display_message(frames, { from_user: 'ERROR', body: err }); display_message(frames, { from_user: 'ERROR', body: err });
}); });
session.on('help', function (cmd, help, ars) {
if (!ars || user.compare_ars(ars)) { session.on('local_help', function (help_topic) {
display_server_message(frames, format('\x01h\x01w/\x01h\x01c%s \x01h\x01w- \x01n\x01w%s', cmd, help)); 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());
} }
}); display_server_message(frames, "\x01k\x01h*.:\x01k\x01h__\x01n\x01w");
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');
}); });
session.on('message', function (msg) { session.on('message', function (msg) {
if (msg.from_user == 'SERVER') { if (msg.from_user == 'SERVER') {
display_server_message(frames, msg.body); display_server_message(frames, msg.body);
} else { } else {
if (session.twit_list.indexOf(msg.from_user.toLowerCase()) < 0) {
var mention = false; var mention = false;
if (msg.body.toUpperCase().indexOf(user.alias.toUpperCase()) >= 0 && msg.from_user !== user.alias) { if (msg.body.toUpperCase().indexOf(user.alias.toUpperCase()) >= 0 && msg.from_user !== user.alias) {
console.beep(); console.beep();
...@@ -332,7 +427,10 @@ function main() { ...@@ -332,7 +427,10 @@ function main() {
session.mention_count = session.mention_count + 1; session.mention_count = session.mention_count + 1;
refresh_stats (frames, session); refresh_stats (frames, session);
} }
display_message(frames, msg, mention); //, nick_colours[msg.from_user] || ''); display_message(frames, msg, mention);
} /*else {
log ( "filtered message from " + msg.from_user + ".");
}*/
} }
}); });
session.on('nicks', function (nicks) { session.on('nicks', function (nicks) {
...@@ -356,19 +454,64 @@ function main() { ...@@ -356,19 +454,64 @@ function main() {
refresh_stats(frames, session); refresh_stats(frames, session);
}); });
if (settings.startup.splash) display_external_text(frames, "splash");
if (settings.startup.motd) session.motd(); 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.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; var cmd, line, user_input;
while (!js.terminated && !break_loop) { while (!js.terminated && !break_loop) {
session.cycle(); session.cycle();
if (input_state == 'chat') {
frames.divider.gotoxy(frames.divider.width - 16, 1); 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 + "]"); 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(); user_input = inputline.getkey();
if (typeof user_input == 'string') { if (typeof user_input == 'string') {
if (input_state == 'chat') { 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 = user_input.split(' ');
cmd[0] = cmd[0].substr(1).toLowerCase(); cmd[0] = cmd[0].substr(1).toLowerCase();
switch (cmd[0]) { switch (cmd[0]) {
...@@ -440,15 +583,33 @@ function main() { ...@@ -440,15 +583,33 @@ function main() {
refresh_stats(frames, session); refresh_stats(frames, session);
break; break;
case "theme": case "theme":
if (cmd.length == 2) {
set_theme( cmd[1] ); set_theme( cmd[1] );
frames.title.attr = theme_bg_color|theme_fg_color; frames.title.attr = theme_bg_color|theme_fg_color;
frames.divider.attr = theme_bg_color|theme_fg_color; frames.divider.attr = theme_bg_color|theme_fg_color;
display_title(frames, session.room, session.room_topic); display_title(frames, session.room, session.room_topic);
refresh_stats(frames, session); 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; break;
default: default:
if (typeof session[cmd[0]] == 'function') { if (typeof session[cmd[0]] == 'function') {
session[cmd[0]](cmd.slice(1).join(' ')); 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; break;
} }
...@@ -475,10 +636,32 @@ function main() { ...@@ -475,10 +636,32 @@ function main() {
} else if ( // Regular message } else if ( // Regular message
typeof user_input == 'string' // Could be bool at this point typeof user_input == 'string' // Could be bool at this point
&& user_input != '' && 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); 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') { } else if (input_state == 'scroll' || input_state == 'scroll_nicks') {
var sframe = input_state == 'scroll' ? frames.output : frames.nicks; var sframe = input_state == 'scroll' ? frames.output : frames.nicks;
if (user_input == KEY_UP && sframe.offset.y > 0) { if (user_input == KEY_UP && sframe.offset.y > 0) {
...@@ -510,8 +693,12 @@ function main() { ...@@ -510,8 +693,12 @@ function main() {
frames.nick_scroll.cycle(); frames.nick_scroll.cycle();
console.gotoxy(inputline.frame.__relations__.child[0].x, inputline.frame.y); console.gotoxy(inputline.frame.__relations__.child[0].x, inputline.frame.y);
} }
display_system_messages(frames);
yield(); yield();
} }
console.clear(autopause=false); // prevent an unintended auto-pause when quitting
} }
main(); main();
...@@ -28,8 +28,8 @@ f = undefined; ...@@ -28,8 +28,8 @@ f = undefined;
if (!settings.ssl) if (!settings.ssl)
settings.ssl=false; settings.ssl=false;
const PROTOCOL_VERSION = '1.3.1'; const PROTOCOL_VERSION = '1.3.2';
const MAX_LINE = 256; const MAX_LINE = 512;
const FROM_SITE = system.name.replace(/ /g, "_"); const FROM_SITE = system.name.replace(/ /g, "_");
const SYSTEM_NAME = system_info.system_name || system.name; const SYSTEM_NAME = system_info.system_name || system.name;
...@@ -221,8 +221,10 @@ function mrc_receive(sock) { ...@@ -221,8 +221,10 @@ function mrc_receive(sock) {
line = sock.recvline(MAX_LINE, settings.timeout); line = sock.recvline(MAX_LINE, settings.timeout);
if (!line || line == '') break; if (!line || line == '') break;
latency_tracker.forEach(function(m) { 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) }); client_send({ from_user: "SERVER", to_user: 'CLIENT', body: 'LATENCY:' + (Date.now() - m.time) });
log(LOG_DEBUG, 'Latency: ' + (Date.now() - m.time) );
latency_tracker = []; latency_tracker = [];
} }
}); });
...@@ -239,7 +241,6 @@ function mrc_receive(sock) { ...@@ -239,7 +241,6 @@ function mrc_receive(sock) {
fMa.write(message.body.substr(message.body.indexOf(':')+1).trim()); fMa.write(message.body.substr(message.body.indexOf(':')+1).trim());
fMa.close(); fMa.close();
} }
if (['', 'ALL', FROM_SITE].indexOf(message.to_site) > -1) {
if (['', 'CLIENT', 'ALL', 'NOTME'].indexOf(message.to_user) > -1) { if (['', 'CLIENT', 'ALL', 'NOTME'].indexOf(message.to_user) > -1) {
// Forward to all clients // Forward to all clients
client_send(message); client_send(message);
...@@ -247,7 +248,6 @@ function mrc_receive(sock) { ...@@ -247,7 +248,6 @@ function mrc_receive(sock) {
// Send to this user // Send to this user
client_send(message, message.to_user); client_send(message, message.to_user);
} }
}
yield(); yield();
} }
} }
...@@ -274,6 +274,7 @@ function main() { ...@@ -274,6 +274,7 @@ function main() {
Object.keys(clients).forEach(function (e, i) { Object.keys(clients).forEach(function (e, i) {
if (!clients[e].socket.is_connected) { 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'); mrc_send(mrc_sock, clients[e].username, '', 'SERVER', '', '', 'LOGOFF');
client_send({ from_user: clients[e].username, to_user: 'SERVER', body: 'LOGOFF' }); // Notify local clients client_send({ from_user: clients[e].username, to_user: 'SERVER', body: 'LOGOFF' }); // Notify local clients
delete clients[e]; delete clients[e];
......
NB__[ HWList of available commandsN: B]_____________________________
WH/CinfoNHK:N View information about a BBSHK:N H/CinfoN H#
WH/Ct Kor W/CmsgK:N Send a direct messageHK:NH/CtN HnickN HBmessage
WH/CrNHK:N Reply to last direct messageHK:N H/CrB message
WH/CjoinNHK:N Move to a new roomHK:W/CjoinN Hroom_name
WH/CtopicNHK:N Change room topic:H/CtopicW topic
WH/CroomsNHK:N List available rooms
WH/CusersNHK:N List users
WH/CwhoonNHK:N List users and BBSes
WH/CmotdNHK: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/CthemeNHK:N Select a theme HK..........N seeHK:N H/ChelpN Htheme
WH/CtwitNHK:N Twit List management HK....N seeHK:N H/ChelpN Htwit
WH/CquitNHK:N Exit the program
WHCLEFTK/CRIGHTWK:N Change your text color HK..N seeHK:N H/ChelpN Hnick
WHCCTRLK+CDNHK:N Clear the input line
NFor a list of additional HSERVERN commands, see H/CquoteN Hhelp
\ No newline at end of file
NB__[ HWNick customization optionsN: B]_____________________________
HW/Cnick_prefix K:W NSet a single-character prefix for your handle
Neg.H /Cnick_prefixN H@
WH/Cnick_colorN HK:W NSet your nick color
HNG02HW NC03HW NR04HW NM05HW NY06HW B09W G10W C11W R12M 13W Y14W 15
WH/Cnick_suffix K:W NSet an eight-character suffix for your handle
Neg.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>
NB__[ HWHow to set a custom themeN: B]______________________________
N Usage:
WH/Ctheme NHnameoftheme
N
\ No newline at end of file
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
...@@ -16,7 +16,10 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -16,7 +16,10 @@ function MRC_Session(host, port, user, pass, alias) {
alias: alias || user, alias: alias || user,
stats: ['-','-','-','0'], stats: ['-','-','-','0'],
mention_count: 0, mention_count: 0,
latency: '-' latency: '-',
msg_color: 7,
twit_list: [],
last_private_msg_from: ""
}; };
const callbacks = { const callbacks = {
...@@ -41,10 +44,10 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -41,10 +44,10 @@ function MRC_Session(host, port, user, pass, alias) {
function send_message(to_user, to_room, body) { function send_message(to_user, to_room, body) {
if (body.length + state.alias.length + 1 > 250) { if (body.length + state.alias.length + 1 > 250) {
word_wrap(body, 250 - 1 - state.alias.length).split(/\n/).forEach(function (e) { 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 { } 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) { ...@@ -80,7 +83,7 @@ function MRC_Session(host, port, user, pass, alias) {
break; break;
case 'STATS': case 'STATS':
state.stats = params.split(' '); state.stats = params.split(' ');
emit('stats'); //, state.stats); emit('stats');
break; break;
case 'LATENCY': case 'LATENCY':
state.latency = params; state.latency = params;
...@@ -90,6 +93,10 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -90,6 +93,10 @@ function MRC_Session(host, port, user, pass, alias) {
emit('message', msg); emit('message', msg);
break; 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') { } else if (msg.to_user == 'SERVER') {
if (msg.body == 'LOGOFF') { if (msg.body == 'LOGOFF') {
uidx = state.nicks.indexOf(msg.from_user); uidx = state.nicks.indexOf(msg.from_user);
...@@ -99,6 +106,11 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -99,6 +106,11 @@ function MRC_Session(host, port, user, pass, alias) {
} }
} }
} else if (msg.to_user == '' || user.toLowerCase() == msg.to_user.toLowerCase()) { } 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) { if (msg.to_room == '' || msg.to_room == state.room) {
emit('message', msg); emit('message', msg);
} }
...@@ -112,9 +124,9 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -112,9 +124,9 @@ function MRC_Session(host, port, user, pass, alias) {
state.nicks.splice(uidx, 1); state.nicks.splice(uidx, 1);
emit('nicks', state.nicks); emit('nicks', state.nicks);
} }
} else if (msg.body.search(/just joined room/) > -1) { } /*else if (msg.body.search(/just joined room/) > -1) {
send_command('USERLIST', 'ALL'); send_command('USERLIST', 'ALL'); // moved above to msg.from_user == 'SERVER'
} }*/
emit('message', msg); emit('message', msg);
} }
} }
...@@ -123,6 +135,10 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -123,6 +135,10 @@ function MRC_Session(host, port, user, pass, alias) {
send_message('', state.room, msg); send_message('', state.room, msg);
} }
this.send_notme = function (msg) {
send("NOTME", "", "", msg);
}
this.send_private_messsage = function (user, msg) { this.send_private_messsage = function (user, msg) {
msg = '|11(|03Private|11)|07 ' + msg; msg = '|11(|03Private|11)|07 ' + msg;
send_message(user, state.room, msg); send_message(user, state.room, msg);
...@@ -140,7 +156,11 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -140,7 +156,11 @@ function MRC_Session(host, port, user, pass, alias) {
password: pass, password: pass,
alias: state.alias alias: state.alias
}) + '\r\n'); }) + '\r\n');
this.send_command('IAMHERE');
mswait(20);
this.send_command('USERLIST', 'ALL'); this.send_command('USERLIST', 'ALL');
mswait(20);
this.send_command('STATS', 'ALL'); this.send_command('STATS', 'ALL');
} }
...@@ -181,34 +201,40 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -181,34 +201,40 @@ function MRC_Session(host, port, user, pass, alias) {
} }
const commands = { const commands = {
banners: {
help: 'List of banners from server' // Doesn't do anything? // This section has been cleaned up significantly.
}, //
chatters: { // Server commands no longer need to be strictly defined
help: 'List current users' // here.
}, //
bbses: { // Some commands are still defined if they have specific
help: 'List connected BBSs', // function calls attached to them in mrc-client.js
command: 'CONNECTED' // (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: {
help: 'Display this help message', //help: 'Display this help message',
callback: function (str) { callback: function (str) {
emit('help', 'List of available commands:', ''); emit('local_help', str);
for (var c in commands) {
emit('help', c, commands[c].help, commands[c].ars);
}
emit('local_help');
} }
}, },
info: { info: {
help: 'View information about a BBS (/info #)', //help: 'View information about a BBS (/info #)',
callback: function (str) { callback: function (str) {
this.send_command('INFO ' + str); this.send_command('INFO ' + str);
} }
}, },
join: { join: {
help: 'Move to a new room: /join room_name', //help: 'Move to a new room: /join room_name',
callback: function (str) { // validate valid 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)); this.send_command(format('NEWROOM:%s:%s', state.room, str));
...@@ -217,14 +243,11 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -217,14 +243,11 @@ function MRC_Session(host, port, user, pass, alias) {
this.send_command('USERLIST', 'ALL'); this.send_command('USERLIST', 'ALL');
} }
}, },
meetups: {
help: 'Display information about upcoming meetups'
},
motd: { motd: {
help: 'Display the Message of the Day' //help: 'Display the Message of the Day'
}, },
msg: { msg: { // This is largely overtaken by /t, but we'll still handle it.
help: 'Send a private message: /msg nick message goes here', //help: 'Send a private message: /msg nick message goes here',
callback: function (str) { callback: function (str) {
const cmd = str.split(' '); const cmd = str.split(' ');
if (cmd.length > 1 && state.nicks.indexOf(cmd[0]) > -1) { if (cmd.length > 1 && state.nicks.indexOf(cmd[0]) > -1) {
...@@ -232,8 +255,8 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -232,8 +255,8 @@ function MRC_Session(host, port, user, pass, alias) {
} }
} }
}, },
t: { // added as shorthand for /msg t: {
help: 'Shorthand for /msg: /t nick message goes here', //help: 'Send a private message: /t nick message goes here',
callback: function (str) { callback: function (str) {
const cmd = str.split(' '); const cmd = str.split(' ');
if (cmd.length > 1 && state.nicks.indexOf(cmd[0]) > -1) { if (cmd.length > 1 && state.nicks.indexOf(cmd[0]) > -1) {
...@@ -241,70 +264,41 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -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: { quote: {
help: 'Send a raw command to the server', //help: 'Send a raw command to the server',
callback: function (str) { callback: function (str) {
this.send_command(str); this.send_command(str);
} }
}, },
quit: { quit: {
help: 'Quit the program', //help: 'Quit the program',
callback: function () { callback: function () {
handle.close();
emit('disconnect'); emit('disconnect');
handle.close();
} }
}, },
rooms: { rooms: {
help: 'List available rooms', //help: 'List available rooms',
command: 'LIST' command: 'LIST'
}, },
stats: { stats: {
help: 'Return anonymous server stats', //help: 'Return anonymous server stats',
command: 'statistics' command: 'statistics'
}, },
topic: { topic: {
help: 'Change the topic of the current room', //help: 'Change the topic of the current room',
callback: function (str) { callback: function (str) {
this.send_command(format('NEWTOPIC:%s:%s', state.room, 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) { Object.keys(commands).forEach(function (e) {
...@@ -312,11 +306,11 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -312,11 +306,11 @@ function MRC_Session(host, port, user, pass, alias) {
this[e] = commands[e].callback; this[e] = commands[e].callback;
} else if (commands[e].command) { } else if (commands[e].command) {
this[e] = function () { this[e] = function () {
this.send_command(commands[e].command.toUpperCase()); this.send_command(commands[e].command);
} }
} else { } else {
this[e] = function () { this[e] = function () {
this.send_command(e.toUpperCase()); this.send_command(e);
} }
} }
}, this); }, this);
......
NHK.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"NB.HK W.N . HK.
N.HK W.K NB.W B.W B8H88N B8H88 NB8H88 NB8H88NB8H88NB.W B.HK W.N .
HK . N.HK W.K NB.W B8H88N B8H88 NB8H88 NB8H88NBY8H8b.NB.HK W.N . HK.
. .N . H.N B8H88N B8H88 NB8H88 NB8H88NB"Y8H888 W.N . HK.N HK.
` .N HK.N HK=N==H=========================N==HK=N HK.N HK. '
NHK-=[N HCMNCulti HRNCelay HCNChat forW HCSynchronetN HK]=-
...@@ -60,7 +60,6 @@ colour codes are permitted: ...@@ -60,7 +60,6 @@ colour codes are permitted:
http://wiki.mysticbbs.com/doku.php?id=displaycodes#color_codes_pipe_colors 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: mrc-connector.ini:
...@@ -77,11 +76,16 @@ mrc-client.ini: ...@@ -77,11 +76,16 @@ mrc-client.ini:
above instructions while editing '/sbbs/ctrl/services.ini'. above instructions while editing '/sbbs/ctrl/services.ini'.
- The 'ping_interval' setting should be left at the default value unless you - The 'ping_interval' setting should be left at the default value unless you
have a good reason for changing it. have a good reason for changing it.
- The values in the [startup] section determine which room the client joins - The values in the [startup] section control the following:
on startup, and whether the Message of the Day and banners are displayed. room: Which room the client joins on startup.
- Change show_nicks in the [client] section to always display the nick list motd: Whether the Message of the Day is displayed.
when connecting to the MRC server. 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 4) MRC Stats
...@@ -123,19 +127,35 @@ see "Yes" next to your BBS in the SSL column. ...@@ -123,19 +127,35 @@ see "Yes" next to your BBS in the SSL column.
6) Themes 6) Themes
MRC comes with several customizable theme files. These can be added/edited as 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: matching the pattern:
mrc-theme-<theme_name>.ini 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 /theme <name>, and the selected theme gets saved to settings for future
sessions. sessions.
If a theme file is not available, the classic blue theme gets used by default. 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 - Post a message to 'echicken' in the Synchronet Sysops area on DOVE-Net
- Find me on irc.synchro.net in #synchronet - Find me on irc.synchro.net in #synchronet
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment