diff --git a/core/mrc.js b/core/mrc.js index 5320c999..54aa70f7 100644 --- a/core/mrc.js +++ b/core/mrc.js @@ -5,16 +5,17 @@ const Log = require('./logger.js').log; const { MenuModule } = require('./menu_module.js'); const { - pipeToAnsi + pipeToAnsi, + stripMciColorCodes } = require('./color_codes.js'); -const stringFormat = require('./string_format.js'); -const StringUtil = require('./string_util.js'); +const stringFormat = require('./string_format.js'); +const StringUtil = require('./string_util.js'); // deps const _ = require('lodash'); const async = require('async'); -const net = require('net'); -const moment = require('moment'); +const net = require('net'); +const moment = require('moment'); exports.moduleInfo = { name : 'MRC Client', @@ -107,6 +108,12 @@ exports.getModule = class mrcModule extends MenuModule { this.viewControllers.mrcChat.switchFocus(MciViewIds.mrcChat.inputArea); return cb(null); + }, + + quit : (formData, extraArgs, cb) => { + this.sendServerMessage('LOGOFF'); + this.state.socket.destroy(); + return this.prevMenu(cb); } }; } @@ -167,12 +174,23 @@ exports.getModule = class mrcModule extends MenuModule { */ addMessageToChatLog(message) { const chatLogView = this.viewControllers.mrcChat.getView(MciViewIds.mrcChat.chatLog); - chatLogView.addText(pipeToAnsi(message)); + const messageLength = stripMciColorCodes(message).length; + const chatWidth = chatLogView.dimens.width; + let padAmount = 0; + + if (messageLength > chatWidth) { + padAmount = chatWidth - (messageLength % chatWidth); + } else { + padAmount = chatWidth - messageLength; + } + + const padding = ' |00' + ' '.repeat(padAmount - 2); + chatLogView.addText(pipeToAnsi(message + padding)); } /** - * Processes data received back from the MRC multiplexer - */ + * Processes data received from the MRC multiplexer + */ processReceivedMessage(blob) { blob.split('\n').forEach( message => { @@ -200,13 +218,13 @@ exports.getModule = class mrcModule extends MenuModule { this.state.nicks = params[1].split(','); break; - case 'STATS': + case 'STATS': { const stats = params[1].split(' '); this.viewControllers.mrcChat.getView(MciViewIds.mrcChat.mrcUsers).setText(stats[2]); this.viewControllers.mrcChat.getView(MciViewIds.mrcChat.mrcBbses).setText(stats[0]); this.state.last_ping = stats[1]; - break; + } default: this.addMessageToChatLog(message.body); @@ -214,20 +232,10 @@ exports.getModule = class mrcModule extends MenuModule { } } else { - if (message.from_user == this.state.alias && message.to_user == 'NOTME') { - // don't deliver NOTME messages - return; - } else { + if (message.to_room == this.state.room) { // if we're here then we want to show it to the user const currentTime = moment().format(this.client.currentTheme.helpers.getTimeFormat()); - - if (message.to_user == this.state.alias) { - // it's a pm - this.addMessageToChatLog('|08' + currentTime + ' |14PM|00 ' + message.body + '|00'); - } else { - // it's not a pm - this.addMessageToChatLog('|08' + currentTime + '|00 ' + message.body + '|00'); - } + this.addMessageToChatLog('|08' + currentTime + '|00 ' + message.body + '|00'); } } @@ -240,27 +248,39 @@ exports.getModule = class mrcModule extends MenuModule { */ processOutgoingMessage(message, to_user) { if (message.startsWith('/')) { - this.processSlashCommand(message); - } else { if (message == '') { + // don't do anything if message is blank, just update stats this.sendServerMessage('STATS'); return; } - // just format and send + // else just format and send const textFormatObj = { fromUserName : this.state.alias, + toUserName : to_user, message : message }; const messageFormat = this.config.messageFormat || '|00|10<|02{fromUserName}|10>|00 |03{message}|00'; + + const privateMessageFormat = + this.config.outgoingPrivateMessageFormat || + '|00|10<|02{fromUserName}|10|14->|02{toUserName}>|00 |03{message}|00'; + let formattedMessage = ''; + if (to_user == undefined) { + // normal message + formattedMessage = stringFormat(messageFormat, textFormatObj); + } else { + // pm + formattedMessage = stringFormat(privateMessageFormat, textFormatObj); + } + try { - const formattedMessage = stringFormat(messageFormat, textFormatObj); this.sendMessageToMultiplexer(to_user || '', '', this.state.room, formattedMessage); } catch(e) { this.client.log.warn( { error : e.message }, 'MRC error'); @@ -283,7 +303,7 @@ exports.getModule = class mrcModule extends MenuModule { case 'pm': this.processOutgoingMessage(cmd[2], cmd[1]); break; - case 'rainbow': + case 'rainbow': { // this is brutal, but i love it const line = message.replace(/^\/rainbow\s/, '').split(' ').reduce(function (a, c) { const cc = Math.floor((Math.random() * 31) + 1).toString().padStart(2, '0'); @@ -293,17 +313,17 @@ exports.getModule = class mrcModule extends MenuModule { this.processOutgoingMessage(line); break; - + } case 'l33t': - this.processOutgoingMessage(StringUtil.stylizeString(message.substr(5), 'l33t')); + this.processOutgoingMessage(StringUtil.stylizeString(message.substr(6), 'l33t')); break; - case 'kewl': + case 'kewl': { const text_modes = Array('f','v','V','i','M'); const mode = text_modes[Math.floor(Math.random() * text_modes.length)]; - this.processOutgoingMessage(StringUtil.stylizeString(message.substr(5), mode)); + this.processOutgoingMessage(StringUtil.stylizeString(message.substr(6), mode)); break; - + } case 'whoon': this.sendServerMessage('WHOON'); break; diff --git a/core/servers/chat/mrc_multiplexer.js b/core/servers/chat/mrc_multiplexer.js index 9410a237..7eaac22f 100644 --- a/core/servers/chat/mrc_multiplexer.js +++ b/core/servers/chat/mrc_multiplexer.js @@ -2,10 +2,12 @@ 'use strict'; // ENiGMA½ -const Log = require('../../logger.js').log; -const { ServerModule } = require('../../server_module.js'); -const Config = require('../../config.js').get; -const { Errors } = require('../../enig_error.js'); +const Log = require('../../logger.js').log; +const { ServerModule } = require('../../server_module.js'); +const Config = require('../../config.js').get; +const { Errors } = require('../../enig_error.js'); +const SysProps = require('../../system_property.js'); +const StatLog = require('../../stat_log.js'); // deps const net = require('net'); @@ -30,9 +32,7 @@ let mrcCentralConnection = ''; exports.getModule = class MrcModule extends ServerModule { constructor() { super(); - this.log = Log.child( { server : 'MRC' } ); - } createServer(cb) { @@ -43,12 +43,12 @@ exports.getModule = class MrcModule extends ServerModule { const self = this; const config = Config(); - const boardName = config.general.boardName; + const boardName = config.general.prettyBoardName || config.general.boardName; const enigmaVersion = 'ENiGMA-BBS_' + require('../../../package.json').version; const mrcConnectOpts = { - port : 50000, - host : 'mrc.bottomlessabyss.net' + host : config.chatServers.mrc.serverHostname || 'mrc.bottomlessabyss.net', + port : config.chatServers.mrc.serverPort || 5000 }; const handshake = `${boardName}~${enigmaVersion}/${os.platform()}-${os.arch()}/${PROTOCOL_VERSION}`; @@ -91,7 +91,6 @@ exports.getModule = class MrcModule extends ServerModule { socket.on('data', data => { // split on \n to deal with getting messages in batches data.toString().split('\n').forEach( item => { - if (item == '') return; // save username with socket @@ -103,7 +102,6 @@ exports.getModule = class MrcModule extends ServerModule { self.receiveFromClient(socket.username, item); } }); - }); socket.on('end', function() { @@ -117,7 +115,6 @@ exports.getModule = class MrcModule extends ServerModule { }); }); - return cb(null); } @@ -146,22 +143,31 @@ exports.getModule = class MrcModule extends ServerModule { return _.isNumber(_.get(config, 'chatServers.mrc.multiplexerPort')); } + /** + * Sends received messages to local clients + */ sendToClient(message) { connectedSockets.forEach( (client) => { - if (message.to_user == '' || message.to_user == client.username || message.to_user == 'CLIENT') { - // this.log.debug({ server : 'MRC', username : client.username, message : message }, 'Forwarding message to connected user') + if (message.to_user == '' || message.to_user == client.username || message.to_user == 'CLIENT' || message.from_user == client.username || message.to_user == 'NOTME' ) { + this.log.debug({ server : 'MRC', username : client.username, message : message }, 'Forwarding message to connected user'); client.write(JSON.stringify(message) + '\n'); + } else { + this.log.debug({ server : 'MRC', username : client.username, message : message }, 'Not forwarding message'); } }); } + /** + * Processes messages received // split raw data received into an object we can work withfrom the central MRC server + */ receiveFromMRC(socket, message) { const config = Config(); const siteName = slugify(config.general.boardName); if (message.from_user == 'SERVER' && message.body == 'HELLO') { - // initial server hello, can ignore + // reply with extra bbs info + this.sendToMrcServer(socket, 'CLIENT', '', 'SERVER', 'ALL', '', `INFOSYS:${StatLog.getSystemStat(SysProps.SysOpUsername)}`); } else if (message.from_user == 'SERVER' && message.body.toUpperCase() == 'PING') { // reply to heartbeat @@ -174,7 +180,9 @@ exports.getModule = class MrcModule extends ServerModule { } } - // split raw data received into an object we can work with + /** + * Takes an MRC message and parses it into something usable + */ parseMessage(line) { const msg = line.split('~'); if (msg.length < 7) { @@ -192,6 +200,9 @@ exports.getModule = class MrcModule extends ServerModule { }; } + /** + * Receives a message from a local client and sanity checks before sending on to the central MRC server + */ receiveFromClient(username, message) { try { message = JSON.parse(message); @@ -202,7 +213,9 @@ exports.getModule = class MrcModule extends ServerModule { this.sendToMrcServer(mrcCentralConnection, message.from_user, message.from_room, message.to_user, message.to_site, message.to_room, message.body); } - // send a message back to the mrc central server + /** + * Converts a message back into the MRC format and sends it to the central MRC server + */ sendToMrcServer(socket, fromUser, fromRoom, toUser, toSite, toRoom, messageBody) { const config = Config(); const siteName = slugify(config.general.boardName); @@ -222,8 +235,9 @@ exports.getModule = class MrcModule extends ServerModule { } }; - -// User / site name must be ASCII 33-125, no MCI, 30 chars max, underscores +/** + * User / site name must be ASCII 33-125, no MCI, 30 chars max, underscores + */ function sanitiseName(str) { return str.replace( /\s/g, '_' @@ -242,6 +256,9 @@ function sanitiseMessage(message) { return message.replace(/[^\x20-\x7D]/g, ''); } +/** + * SLugifies the BBS name for use as an MRC "site name" + */ function slugify(text) { return text.toString() .replace(/\s+/g, '_') // Replace spaces with _