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

Merge branch 'master' into 'master'

CTCP, user actions, and fixes

See merge request !525
parents 8253d206 ddcd5013
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
); );
...@@ -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();
...@@ -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
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
// 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 = {
...@@ -51,6 +55,63 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -51,6 +55,63 @@ function MRC_Session(host, port, user, pass, alias) {
} }
} }
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;
const rest = Array.prototype.slice.call(arguments, 1); const rest = Array.prototype.slice.call(arguments, 1);
...@@ -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) {
...@@ -139,6 +218,10 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -139,6 +218,10 @@ function MRC_Session(host, port, user, pass, alias) {
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;
send_message(user, state.room, msg); send_message(user, state.room, 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,6 +402,31 @@ function MRC_Session(host, port, user, pass, alias) { ...@@ -298,6 +402,31 @@ 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") + '.');
}
} }
}; };
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment