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");
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,7 @@ if (!f.open('r')) {
alert("Error " + f.error + " (" + strerror(f.error) + ") opening " + f.name);
exit(1);
}
const settings = {
root: f.iniGetObject(),
startup: f.iniGetObject('startup'),
......@@ -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.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 +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;
if (frames.output.data_height > frames.output.height) {
while (frames.output.down()) {
......@@ -196,7 +206,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 +438,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 +466,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 +491,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 +624,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 +689,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 +719,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