Skip to content
Snippets Groups Projects
Commit d5edd3c3 authored by Craig Hendricks's avatar Craig Hendricks
Browse files

Change List:

 Enhancements:
 
  - /q added as a shortcut for /quit
  - /? added as a shortcut for /quote help
 

 Fixes:
 
  - Incoming messages don't get added to the chat window while scrolling or reviewing mentions. Instead, they get pushed into a temporary buffer, and then added to the chat window when finished scrolling/reviewing. This fixes an issue with laggy scrolling with lots of activity in the room.

  - Fixed a typo in the twit help file.
 
 
 New Features:
 
  - Basic CTCP implemented. Syntax:
  
    /ctcp <target> <command>
  
    target:  Can be users or rooms. An asterisk specifies all users in the 
             current room.
             
    command: The CTCP command to be issued. Supported commands:
      
      - VERSION:    Returns the user's MRC client version information.
      - TIME:       Returns the client's (BBS's) current local date/time.
      - PING:       Doesn't seem fully implemented; most Mystic clients return 
                    an empty string.
      - CLIENTINFO: Returns list of commands supported by the remote client.
    
    Example usage:
    
      /ctcp * time
      
    Output returned:
    
      * [CTCP-REPLY] StackFault TIME 03/31/25 14:49     
      * [CTCP-REPLY] geekboy TIME 03/31/25 14:49        
      * [CTCP-REPLY] mafiath TIME 03/31/25 14:49        
      * [CTCP-REPLY] k9zw TIME 03/31/25 19:49           
      * [CTCP-REPLY] zharvek TIME 03/31/25 14:49
      
    User can hide/show incoming CTCP requests with /toggle_ctcp.
    They're shown by default.
    
 
 
  - User actions.
  
    User types /me to perform an action in the room.
    
    Example usage:
    
      /me sips coffee
    
    Message sent to chat room:
    
      * Codefenix sips coffee
 
 
 Housekeeping:
 
 - Updated help files. Moved some nonessential commands to /help more (noted in main /help).
 
parent c011d987
Branches
No related tags found
No related merge requests found
Pipeline #8864 passed
......@@ -48,6 +48,7 @@ js.on_exit("js.counter = 0");
js.time_limit=0;
var input_state = 'chat';
var paused_msg_buffer = [];
var show_nicks = false;
var stat_mode = 0; // 0 = Local Session stats (Chatters, Latency, & Mentions); 1 = Global MRC Stats
......@@ -68,6 +69,8 @@ if (!f.open('r')) {
alert("Error " + f.error + " (" + strerror(f.error) + ") opening " + f.name);
exit(1);
}
f.open('r');
const settings = {
root: f.iniGetObject(),
startup: f.iniGetObject('startup'),
......@@ -140,7 +143,7 @@ function init_display(msg_color) {
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_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.input = new Frame(2, h, w-2, 1, BG_BLACK|WHITE, f.top);
f.output_scroll = new ScrollBar(f.output, { autohide: true });
f.nick_scroll = new ScrollBar(f.nicks, { autohide: true });
f.output.word_wrap = true;
......@@ -180,7 +183,15 @@ function refresh_stats(frames, session) {
}
}
function append_message(frames, msg, mention) {
function append_message(frames, msg, mention, when) {
if (input_state !== "chat") { // pause incoming messages while scrolling.
paused_msg_buffer.push({"msg": msg, // we'll capture any incoming messages in the meantime
"mention": mention, // and display them when done scrolling.
"when": new Date()});
return;
}
const top = frames.output.offset.y;
if (frames.output.data_height > frames.output.height) {
while (frames.output.down()) {
......@@ -196,7 +207,7 @@ function append_message(frames, msg, mention) {
}
frames.output.putmsg(
(mention ? "\x01k\x017" : "\x01k\x01h") + getShortTime(new Date()) + // timestamp formatting
(mention ? "\x01k\x017" : "\x01k\x01h") + getShortTime(when || new Date()) + // timestamp formatting
(mention ? ( "\x01n\x01r\x01h\x01i" + MENTION_MARKER ) : " ") + // mention formatting
"\x01n" + msg + '\r\n' // message itself
);
......@@ -428,7 +439,7 @@ function main() {
console.beep();
mention = true;
session.mention_count = session.mention_count + 1;
refresh_stats (frames, session);
refresh_stats(frames, session);
}
display_message(frames, msg, mention);
} /*else {
......@@ -456,6 +467,9 @@ function main() {
session.on('latency', function () {
refresh_stats(frames, session);
});
session.on('ctcp-msg', function (msg) {
display_server_message(frames, pipeToCtrlA( msg ) );
});
if (settings.startup.splash) display_external_text(frames, "splash");
if (settings.startup.motd) session.motd();
......@@ -478,14 +492,14 @@ function main() {
var cmd, line, user_input;
var lastnodechk = time();
while (!js.terminated && !break_loop) {
if ((time() - lastnodechk) >= 10) {
// Check the node "interrupt flag" once every 10 seconds
if (system.node_list[bbs.node_num - 1].misc & NODE_INTR) {
bbs.nodesync(); // this will display a message to to the user and disconnect
break;
}
lastnodechk = time();
}
if ((time() - lastnodechk) >= 10) {
// Check the node "interrupt flag" once every 10 seconds
if (system.node_list[bbs.node_num - 1].misc & NODE_INTR) {
bbs.nodesync(); // this will display a message to to the user and disconnect
break;
}
lastnodechk = time();
}
session.cycle();
if (input_state == 'chat') {
frames.divider.gotoxy(frames.divider.width - 16, 1);
......@@ -611,6 +625,9 @@ function main() {
manage_twits( cmd[1].toLowerCase(), cmd.slice(2).join(' ').toLowerCase(), session.twit_list, frames );
}
break;
case "?": // shortcut for "help", because why not?
session.send_command("help");
break;
default:
if (typeof session[cmd[0]] == 'function') {
session[cmd[0]](cmd.slice(1).join(' '));
......@@ -673,6 +690,12 @@ function main() {
input_state = 'chat';
session.mention_count = 0;
refresh_stats(frames, session);
if (paused_msg_buffer.length > 0) {
for (var pmb in paused_msg_buffer) {
append_message(frames, paused_msg_buffer[pmb].msg, paused_msg_buffer[pmb].mention, paused_msg_buffer[pmb].when);
}
paused_msg_buffer = [];
}
}
} else if (input_state == 'scroll' || input_state == 'scroll_nicks') {
var sframe = input_state == 'scroll' ? frames.output : frames.nicks;
......@@ -697,6 +720,12 @@ function main() {
frames.output_scroll.cycle();
input_state = 'chat';
refresh_stats(frames, session);
if (paused_msg_buffer.length > 0) {
for (var pmb in paused_msg_buffer) {
append_message(frames, paused_msg_buffer[pmb].msg, paused_msg_buffer[pmb].mention, paused_msg_buffer[pmb].when);
}
paused_msg_buffer = [];
}
}
}
}
......
......@@ -28,7 +28,7 @@ f = undefined;
if (!settings.ssl)
settings.ssl=false;
const PROTOCOL_VERSION = '1.3.2';
const PROTOCOL_VERSION = '1.3.3';
const MAX_LINE = 512;
const FROM_SITE = system.name.replace(/ /g, "_");
const SYSTEM_NAME = system_info.system_name || system.name;
......
NBH__[ HWHow to use CTCP commandsN: BH]_______________________________
N CTCP (Client-to-Client Protocol) commands are special messages
N that can be sent to a channel or other clients.
N Usage:
WH/Cctcp NHtarget HBcommand K
N A Htarget Nis a user or room name. HB* Ntargets all.
N Supported commands:
HB VERSION TIME PING CLIENTINFO
N Type WH/Ctoggle_ctcp Nto hide/show incoming requests.
\ No newline at end of file
NB__[ HWList of available commandsN: B]_____________________________
NBH__[ HWList of available commandsN: BH]_____________________________
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/Cmsg Kor W/CtK: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/Cjoin Kor W/CjN HK :N Join a new roomHK:W /CjN Hroom_name
WH/CtopicNHK:N Change room topicHK:HW/CtopicW topic
WH/CmeN HK:N Perform an actionHK: HW/Cme NHwaves
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
WH/CctcpNHK:N Send a CTCP command HK.....N seeHK:N H/ChelpN Hctcp
WH/Cquit Kor W/CqN HK :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
WH/ChelpNHK:N Shows this help list HK....N seeHK:N H/ChelpN Hmore
NFor a list of additional HSERVERN commands, see H/CquoteN Hhelp Nor H/C?
\ No newline at end of file
NBH__[ HWList of additional commandsN: BH]____________________________
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
NB__[ HWHow to use the twit listN: B]_______________________________
NBH__[ HWHow to use the twit listN: B]H_______________________________
N Basic twit list implemented! Hides messages from problematic
N chatters.
N The Htwit list Nhides messages from problematic 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
WH/Ctwit NHlist K:W NLists twits you have added
WH/Ctwit NHclear K:W NEmpties your twit list
......@@ -3,7 +3,10 @@
// Passes traffic between an mrc-connector.js server and a client application
// See mrc-client.js for a bad example.
function MRC_Session(host, port, user, pass, alias) {
const MRC_VER = "Multi Relay Chat JS v1.3.3 2025-04-11 [cf]";
const CTCP_ROOM = "ctcp_echo_channel";
const handle = new Socket();
const state = {
......@@ -19,7 +22,8 @@ function MRC_Session(host, port, user, pass, alias) {
latency: '-',
msg_color: 7,
twit_list: [],
last_private_msg_from: ""
last_private_msg_from: "",
show_ctcp_req: true
};
const callbacks = {
......@@ -50,6 +54,63 @@ function MRC_Session(host, port, user, pass, alias) {
send(to_user, '', to_room, state.alias + ' ' + format("|%02d", state.msg_color) + body);
}
}
function send_ctcp(to, p, s) {
state.output_buffer.push({
from_room: CTCP_ROOM,
to_user: to,
to_site: "",
to_room: CTCP_ROOM,
body: p + " " + user + " " + s
});
mswait(20);
}
function ctcp_time(d) {
return format("%02d/%02d/%02d %02d:%02d", d.getMonth()+1, d.getDate(), d.getFullYear().toString().substr(-2), d.getHours(), d.getMinutes());
}
function ctcp_reply(cmd) {
// Future ctcp commands can be added easily by adding another
// string to this array, and then adding the response logic
// to the switch/case structure below.
const CTCP_CMDS = [
/* 0 */ "VERSION",
/* 1 */ "TIME",
/* 2 */ "PING",
/* 3 */ "CLIENTINFO"
];
var reply = "";
switch (CTCP_CMDS.indexOf(cmd)) {
case 0:
reply = MRC_VER;
break;
case 1:
reply = ctcp_time(new Date());
break;
case 2:
// It's not clear what's supposed to be happening here.
// In the mystic client, PING returns the message string minus
// the sum of the length of the substrings within the string...
// which is an empty string... i.e.: no response
//
// According to https://en.wikipedia.org/wiki/Client-to-client_protocol#PING,
// it's /supposed/ to be the latency between two clients,
// not taking the server into account. Such communication
// within MRC does not presently exist.
//
// So for now, we're just going to return a "PONG" in response.
reply = "PONG";
break;
case 3:
reply = CTCP_CMDS.join(" ");
break;
default:
reply = "Unsupported ctcp command";
break;
}
return reply.trim();
}
function emit() {
if (!Array.isArray(callbacks[arguments[0]])) return;
......@@ -129,6 +190,24 @@ function MRC_Session(host, port, user, pass, alias) {
}*/
emit('message', msg);
}
if (msg.to_room === CTCP_ROOM) {
const ctcp_data = msg.body.split(' ');
if (ctcp_data[0] === "[CTCP]" && ctcp_data.length >= 4) {
if (state.show_ctcp_req) {
emit('ctcp-msg', '* |14[CTCP-REQUEST] |15' + ctcp_data[3] + ' |07on |15' + ctcp_data[2] + ' |07from |10' + msg.from_user);
}
if (ctcp_data[2] === "*" || ctcp_data[2].toUpperCase()===user.toUpperCase() || ctcp_data[2].toUpperCase()==="#"+state.room.toUpperCase() ) {
send_ctcp(ctcp_data[1], "[CTCP-REPLY]", ctcp_data[3].toUpperCase() + " " + ctcp_reply(ctcp_data[3].toUpperCase()) );
}
} else if (ctcp_data[0] === "[CTCP-REPLY]" && ctcp_data.length >= 3) {
if (msg.to_user.toUpperCase()===user.toUpperCase()) {
emit('ctcp-msg', '* |14[CTCP-REPLY] |10' + ctcp_data[1] + ' |15' + ctcp_data.slice(2).join(' ').trim());
}
}
}
}
this.send_room_message = function (msg) {
......@@ -138,6 +217,10 @@ function MRC_Session(host, port, user, pass, alias) {
this.send_notme = function (msg) {
send("NOTME", "", "", msg);
}
this.send_action = function (msg) {
send("", "", state.room, msg);
}
this.send_private_messsage = function (user, msg) {
msg = '|11(|03Private|11)|07 ' + msg;
......@@ -243,6 +326,17 @@ function MRC_Session(host, port, user, pass, alias) {
this.send_command('USERLIST', 'ALL');
}
},
j: { // shorthand for join
// TODO: is there an easier way to duplicate command functionality?
//help: 'Move to a new room: /join room_name',
callback: function (str) { // validate valid room name?
str = str.replace(/^#/, '');
this.send_command(format('NEWROOM:%s:%s', state.room, str));
state.room = str;
state.nicks = [];
this.send_command('USERLIST', 'ALL');
}
},
motd: {
//help: 'Display the Message of the Day'
},
......@@ -255,7 +349,8 @@ function MRC_Session(host, port, user, pass, alias) {
}
}
},
t: {
t: { // shorthand for msg
// TODO: is there an easier way to duplicate command functionality?
//help: 'Send a private message: /t nick message goes here',
callback: function (str) {
const cmd = str.split(' ');
......@@ -263,6 +358,7 @@ function MRC_Session(host, port, user, pass, alias) {
this.send_private_messsage(cmd[0], cmd.slice(1).join(' '));
}
}
},
r: {
//help: 'Reply to last private message: /r message goes here',
......@@ -285,6 +381,14 @@ function MRC_Session(host, port, user, pass, alias) {
handle.close();
}
},
q: { // shorthand for quit
// TODO: is there an easier way to duplicate command functionality?
//help: 'Quit the program',
callback: function () {
emit('disconnect');
handle.close();
}
},
rooms: {
//help: 'List available rooms',
command: 'LIST'
......@@ -298,7 +402,32 @@ function MRC_Session(host, port, user, pass, alias) {
callback: function (str) {
this.send_command(format('NEWTOPIC:%s:%s', state.room, str));
}
}
},
me: {
//me: 'Send an action to the server',
callback: function (str) {
this.send_action('|15* |13' + user + ' ' + str);
}
},
ctcp: {
// outgoing ctcp request
callback: function (str) {
const cmd = str.split(' ');
var u = cmd[0];
if (u) {
if (u === "*" || u.indexOf("#") === 0) {
u = "";
}
send_ctcp(u, '[CTCP]', str.trim().toUpperCase());
}
}
},
toggle_ctcp: {
callback: function() {
state.show_ctcp_req = !state.show_ctcp_req;
emit('ctcp-msg', '* |14Incoming CTCP requests are now ' + (state.show_ctcp_req ? "|15shown" : "|12hidden") + '.');
}
}
};
Object.keys(commands).forEach(function (e) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment