Skip to content
Snippets Groups Projects
Commit ddcd5013 authored by Craig Hendricks's avatar Craig Hendricks Committed by Rob Swindell
Browse files

CTCP, user actions, and fixes

parent 8253d206
No related branches found
No related tags found
1 merge request!525CTCP, user actions, and fixes
...@@ -48,6 +48,7 @@ js.on_exit("js.counter = 0"); ...@@ -48,6 +48,7 @@ js.on_exit("js.counter = 0");
js.time_limit=0; js.time_limit=0;
var input_state = 'chat'; var input_state = 'chat';
var paused_msg_buffer = [];
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
...@@ -68,6 +69,7 @@ if (!f.open('r')) { ...@@ -68,6 +69,7 @@ if (!f.open('r')) {
alert("Error " + f.error + " (" + strerror(f.error) + ") opening " + f.name); alert("Error " + f.error + " (" + strerror(f.error) + ") opening " + f.name);
exit(1); exit(1);
} }
const settings = { const settings = {
root: f.iniGetObject(), root: f.iniGetObject(),
startup: f.iniGetObject('startup'), startup: f.iniGetObject('startup'),
...@@ -140,7 +142,7 @@ function init_display(msg_color) { ...@@ -140,7 +142,7 @@ function init_display(msg_color) {
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_color = new Frame(1, h, 1, 1, BG_BLACK|LIGHTGRAY, 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.input = new Frame(2, h, w-2, 1, BG_BLACK|WHITE, f.top);
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;
...@@ -180,7 +182,15 @@ function refresh_stats(frames, session) { ...@@ -180,7 +182,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; const top = frames.output.offset.y;
if (frames.output.data_height > frames.output.height) { if (frames.output.data_height > frames.output.height) {
while (frames.output.down()) { while (frames.output.down()) {
...@@ -196,7 +206,7 @@ function append_message(frames, msg, mention) { ...@@ -196,7 +206,7 @@ function append_message(frames, msg, mention) {
} }
frames.output.putmsg( 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 (mention ? ( "\x01n\x01r\x01h\x01i" + MENTION_MARKER ) : " ") + // mention formatting
"\x01n" + msg + '\r\n' // message itself "\x01n" + msg + '\r\n' // message itself
); );
...@@ -428,7 +438,7 @@ function main() { ...@@ -428,7 +438,7 @@ function main() {
console.beep(); console.beep();
mention = true; mention = true;
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); display_message(frames, msg, mention);
} /*else { } /*else {
...@@ -456,6 +466,9 @@ function main() { ...@@ -456,6 +466,9 @@ function main() {
session.on('latency', function () { session.on('latency', function () {
refresh_stats(frames, session); 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.splash) display_external_text(frames, "splash");
if (settings.startup.motd) session.motd(); if (settings.startup.motd) session.motd();
...@@ -478,14 +491,14 @@ function main() { ...@@ -478,14 +491,14 @@ function main() {
var cmd, line, user_input; var cmd, line, user_input;
var lastnodechk = time(); var lastnodechk = time();
while (!js.terminated && !break_loop) { while (!js.terminated && !break_loop) {
if ((time() - lastnodechk) >= 10) { if ((time() - lastnodechk) >= 10) {
// Check the node "interrupt flag" once every 10 seconds // Check the node "interrupt flag" once every 10 seconds
if (system.node_list[bbs.node_num - 1].misc & NODE_INTR) { if (system.node_list[bbs.node_num - 1].misc & NODE_INTR) {
bbs.nodesync(); // this will display a message to to the user and disconnect bbs.nodesync(); // this will display a message to to the user and disconnect
break; break;
} }
lastnodechk = time(); lastnodechk = time();
} }
session.cycle(); session.cycle();
if (input_state == 'chat') { if (input_state == 'chat') {
frames.divider.gotoxy(frames.divider.width - 16, 1); frames.divider.gotoxy(frames.divider.width - 16, 1);
...@@ -611,6 +624,9 @@ function main() { ...@@ -611,6 +624,9 @@ function main() {
manage_twits( cmd[1].toLowerCase(), cmd.slice(2).join(' ').toLowerCase(), session.twit_list, frames ); manage_twits( cmd[1].toLowerCase(), cmd.slice(2).join(' ').toLowerCase(), session.twit_list, frames );
} }
break; break;
case "?": // shortcut for "help", because why not?
session.send_command("help");
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(' '));
...@@ -673,6 +689,12 @@ function main() { ...@@ -673,6 +689,12 @@ function main() {
input_state = 'chat'; input_state = 'chat';
session.mention_count = 0; session.mention_count = 0;
refresh_stats(frames, session); 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') { } 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;
...@@ -697,6 +719,12 @@ function main() { ...@@ -697,6 +719,12 @@ function main() {
frames.output_scroll.cycle(); frames.output_scroll.cycle();
input_state = 'chat'; input_state = 'chat';
refresh_stats(frames, session); 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; ...@@ -28,7 +28,7 @@ f = undefined;
if (!settings.ssl) if (!settings.ssl)
settings.ssl=false; settings.ssl=false;
const PROTOCOL_VERSION = '1.3.2'; const PROTOCOL_VERSION = '1.3.3';
const MAX_LINE = 512; 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;
......
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/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/CrNHK:N Reply to last direct messageHK:N H/CrB message
WH/CjoinNHK:N Move to a new roomHK:W/CjoinN Hroom_name WH/Cjoin Kor W/CjN HK :N Join a new roomHK:W /CjN Hroom_name
WH/CtopicNHK:N Change room topic:H/CtopicW topic 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/CroomsNHK:N List available rooms
WH/CusersNHK:N List users WH/CusersNHK:N List users
WH/CwhoonNHK:N List users and BBSes WH/CwhoonNHK:N List users and BBSes
WH/CmotdNHK:N Display the Message of the Day WH/CmotdNHK:N Display the Message of the Day
WH/Cscroll Kor CPGUPK:N Scroll the chat window WH/Cscroll Kor CPGUPK:N Scroll the chat window
WH/Cmentions Kor CUPK:N Reset mention counter and review mentions 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/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/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 WHCLEFTK/CRIGHTWK:N Change your text color HK..N seeHK:N H/ChelpN Hnick
WHCCTRLK+CDNHK:N Clear the input line WHCCTRLK+CDNHK:N Clear the input line
NFor a list of additional HSERVERN commands, see H/CquoteN Hhelp WH/ChelpNHK:N Shows this help list HK....N seeHK:N H/ChelpN Hmore
\ No newline at end of file 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 The Htwit list Nhides messages from problematic chatters.
N chatters.
N Usage: N Usage:
WH/Ctwit NHadd HBnameoftwit K:W NFilters out nameoftwit's messages 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 NHdel HBnameoftwit K:W Nnameoftwit's messages are re-allowed
WH/Ctwit NHlist K:W NLists twits the user has added WH/Ctwit NHlist K:W NLists twits you have added
WH/Ctwit NHclear K:W NEmpty's the user's twit list WH/Ctwit NHclear K:W NEmpties your twit list
...@@ -3,7 +3,10 @@ ...@@ -3,7 +3,10 @@
// Passes traffic between an mrc-connector.js server and a client application // Passes traffic between an mrc-connector.js server and a client application
// See mrc-client.js for a bad example. // See mrc-client.js for a bad example.
function MRC_Session(host, port, user, pass, alias) { 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 handle = new Socket();
const state = { const state = {
...@@ -19,7 +22,8 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -19,7 +22,8 @@ function MRC_Session(host, port, user, pass, alias) {
latency: '-', latency: '-',
msg_color: 7, msg_color: 7,
twit_list: [], twit_list: [],
last_private_msg_from: "" last_private_msg_from: "",
show_ctcp_req: true
}; };
const callbacks = { const callbacks = {
...@@ -50,6 +54,63 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -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); 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() { function emit() {
if (!Array.isArray(callbacks[arguments[0]])) return; if (!Array.isArray(callbacks[arguments[0]])) return;
...@@ -129,6 +190,24 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -129,6 +190,24 @@ function MRC_Session(host, port, user, pass, alias) {
}*/ }*/
emit('message', msg); 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) { this.send_room_message = function (msg) {
...@@ -138,6 +217,10 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -138,6 +217,10 @@ function MRC_Session(host, port, user, pass, alias) {
this.send_notme = function (msg) { this.send_notme = function (msg) {
send("NOTME", "", "", msg); send("NOTME", "", "", msg);
} }
this.send_action = function (msg) {
send("", "", state.room, 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;
...@@ -243,6 +326,17 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -243,6 +326,17 @@ function MRC_Session(host, port, user, pass, alias) {
this.send_command('USERLIST', 'ALL'); 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: { motd: {
//help: 'Display the Message of the Day' //help: 'Display the Message of the Day'
}, },
...@@ -255,7 +349,8 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -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', //help: 'Send a private message: /t nick message goes here',
callback: function (str) { callback: function (str) {
const cmd = str.split(' '); const cmd = str.split(' ');
...@@ -263,6 +358,7 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -263,6 +358,7 @@ function MRC_Session(host, port, user, pass, alias) {
this.send_private_messsage(cmd[0], cmd.slice(1).join(' ')); this.send_private_messsage(cmd[0], cmd.slice(1).join(' '));
} }
} }
}, },
r: { r: {
//help: 'Reply to last private message: /r message goes here', //help: 'Reply to last private message: /r message goes here',
...@@ -285,6 +381,14 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -285,6 +381,14 @@ function MRC_Session(host, port, user, pass, alias) {
handle.close(); 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: { rooms: {
//help: 'List available rooms', //help: 'List available rooms',
command: 'LIST' command: 'LIST'
...@@ -298,7 +402,32 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -298,7 +402,32 @@ function MRC_Session(host, port, user, pass, alias) {
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));
} }
} },
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) { 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