From 9481a7188d13de8b36b56a332977e5694cc131d7 Mon Sep 17 00:00:00 2001 From: Chris Nehren Date: Fri, 2 Jan 2015 15:19:26 -0500 Subject: [PATCH 1/3] Add proper long line wrapping. When sending a message to IRC, the message is relayed to other servers and other clients by the server and needs to fit within the max line length for server messages (512). What this means is that the actual content of the message needs to be truncated based on the length of the user's nick, hostname, and the kind of message being sent (PRIVMSG, NOTICE). The current code, in addition to hardcoding the wrap length at the absolute max line length (not taking any of this into consideration), is rather ugly and hard to follow. I've abstracted the logic into something easier to follow, correct, and reuseable. It'll be easy to expand it to ACTION. TODO: maintain colors and other formatting across wrapped messages. Textual does this for PRIVMSGs, at least, so we can try looking there for inspiration. --- lib/irc.js | 98 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 74 insertions(+), 24 deletions(-) diff --git a/lib/irc.js b/lib/irc.js index be498fe3..6a1f3576 100644 --- a/lib/irc.js +++ b/lib/irc.js @@ -107,6 +107,14 @@ function Client(server, nick, opt) { // (normally this is because you chose something too long and // the server has shortened it self.nick = message.args[0]; + // Note our hostmask to use it in splitting long messages. + // We don't send our hostmask when issuing PRIVMSGs or NOTICEs, + // of course, but rather the servers on the other side will + // include it in messages and will truncate what we send if + // the string is too long. Therefore, we need to be considerate + // neighbors and truncate our messages accordingly. + self.hostMask = message.args[message.args.length - 1]; + self.updateMaxLineLength(); self.emit('registered', message); break; case 'rpl_myinfo': @@ -202,6 +210,7 @@ function Client(server, nick, opt) { self.opt.nickMod++; self.send('NICK', self.opt.nick + self.opt.nickMod); self.nick = self.opt.nick + self.opt.nickMod; + self.updateMaxLineLength(); break; case 'PING': self.send('PONG', message.args[0]); @@ -281,9 +290,11 @@ function Client(server, nick, opt) { }); break; case 'NICK': - if (message.nick == self.nick) + if (message.nick == self.nick) { // the user just changed their own nick self.nick = message.args[0]; + self.updateMaxLineLength(); + } if (self.opt.debug) util.log('NICK: ' + message.nick + ' changes nick to ' + message.args[0]); @@ -831,33 +842,65 @@ Client.prototype.part = function(channel, message, callback) { // {{{ this.send('PART', channel); } }; // }}} -Client.prototype.say = function(target, text) { // {{{ - var self = this; - if (typeof text !== 'undefined') { - text.toString().split(/\r?\n/).filter(function(line) { - return line.length > 0; - }).forEach(function(line) { - var r = new RegExp('.{1,' + self.opt.messageSplit + '}', 'g'), - messagePart; - while ((messagePart = r.exec(line)) !== null) { - self.send('PRIVMSG', target, messagePart[0]); - self.emit('selfMessage', target, messagePart[0]); - } - }); - } -}; // }}} Client.prototype.action = function(channel, text) { // {{{ - var self = this; - if (typeof text !== 'undefined') { - text.toString().split(/\r?\n/).filter(function(line) { - return line.length > 0; - }).forEach(function(line) { - self.say(channel, '\u0001ACTION ' + line + '\u0001'); - }); + var self = this; + if (typeof text !== 'undefined') { + text.toString().split(/\r?\n/).filter(function(line) { + return line.length > 0; + }).forEach(function(line) { + self.say(channel, '\u0001ACTION ' + line + '\u0001'); + }); + } +}; // }}} +Client.prototype._splitLongLines = function(words, maxLength, destination) { // {{{ + if(words.length < maxLength) { + destination.push(words); + return destination; + } + var c = words[maxLength - 1]; + var cutPos; + if(c.match(/\s/)) { + cutPos = maxLength - 1; + } else { + var offset = 1; + while( (maxLength - offset) > 0) { + var c = words[maxLength - offset]; + if(c.match(/\s/)) { + cutPos = maxLength - offset; + break; + } + offset++; + } + if(maxLength - offset <= 0) { + cutPos = maxLength; } + } + var part = words.substring(0, cutPos); + destination.push(part); + return this._splitLongLines(words.substring(cutPos + 1, words.length), maxLength, destination); +}; // }}} +Client.prototype.say = function(target, text) { // {{{ + this._speak('PRIVMSG', target, text); }; // }}} Client.prototype.notice = function(target, text) { // {{{ - this.send('NOTICE', target, text); + this._speak('NOTICE', target, text); +}; // }}} +Client.prototype._speak = function(kind, target, text) { // {{{ + var self = this; + var maxLength = this.maxLineLength - target.length; + if (typeof text !== 'undefined') { + text.toString().split(/\r?\n/).filter(function(line) { + return line.length > 0; + }).forEach(function(line) { + var linesToSend = self._splitLongLines(line, maxLength, []); + linesToSend.forEach(function(toSend) { + self.send(kind, target, toSend); + if(kind == 'PRIVMSG') { + self.emit('selfMessage', target, toSend); + } + }); + }); + } }; // }}} Client.prototype.whois = function(nick, callback) { // {{{ if (typeof callback === 'function') { @@ -905,6 +948,13 @@ Client.prototype.ctcp = function(to, type, text) { return this[type === 'privmsg' ? 'say' : 'notice'](to, '\1' + text + '\1'); }; +// blatantly stolen from irssi's splitlong.pl. Thanks, Bjoern Krombholz! +Client.prototype.updateMaxLineLength = function () { + // 497 = 510 - (":" + "!" + " PRIVMSG " + " :").length; + // target is determined in _speak() and subtracted there + this.maxLineLength = 497 - this.nick.length - this.hostMask.length; +}; + /* * parseMessage(line, stripColors) * From 2e074de883fba15a9cae9e256217d93e25c922ea Mon Sep 17 00:00:00 2001 From: Chris Nehren Date: Sun, 4 Jan 2015 19:06:30 -0500 Subject: [PATCH 2/3] Update formatting, fix line length calculation This fixes the style to be more consistent with the existing code and makes the line length calculation actually take the target into consideration. It also makes some methods private according to whether they are called by the end user or not. --- lib/irc.js | 115 ++++++++++++++++++++++++++--------------------------- 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/lib/irc.js b/lib/irc.js index 6a1f3576..51d005c0 100644 --- a/lib/irc.js +++ b/lib/irc.js @@ -114,7 +114,7 @@ function Client(server, nick, opt) { // the string is too long. Therefore, we need to be considerate // neighbors and truncate our messages accordingly. self.hostMask = message.args[message.args.length - 1]; - self.updateMaxLineLength(); + self._updateMaxLineLength(); self.emit('registered', message); break; case 'rpl_myinfo': @@ -210,7 +210,7 @@ function Client(server, nick, opt) { self.opt.nickMod++; self.send('NICK', self.opt.nick + self.opt.nickMod); self.nick = self.opt.nick + self.opt.nickMod; - self.updateMaxLineLength(); + self._updateMaxLineLength(); break; case 'PING': self.send('PONG', message.args[0]); @@ -293,7 +293,7 @@ function Client(server, nick, opt) { if (message.nick == self.nick) { // the user just changed their own nick self.nick = message.args[0]; - self.updateMaxLineLength(); + self._updateMaxLineLength(); } if (self.opt.debug) @@ -843,65 +843,65 @@ Client.prototype.part = function(channel, message, callback) { // {{{ } }; // }}} Client.prototype.action = function(channel, text) { // {{{ - var self = this; - if (typeof text !== 'undefined') { - text.toString().split(/\r?\n/).filter(function(line) { - return line.length > 0; - }).forEach(function(line) { - self.say(channel, '\u0001ACTION ' + line + '\u0001'); - }); - } -}; // }}} + var self = this; + if (typeof text !== 'undefined') { + text.toString().split(/\r?\n/).filter(function(line) { + return line.length > 0; + }).forEach(function(line) { + self.say(channel, '\u0001ACTION ' + line + '\u0001'); + }); + } +} // }}} Client.prototype._splitLongLines = function(words, maxLength, destination) { // {{{ - if(words.length < maxLength) { - destination.push(words); - return destination; - } - var c = words[maxLength - 1]; - var cutPos; - if(c.match(/\s/)) { - cutPos = maxLength - 1; - } else { - var offset = 1; - while( (maxLength - offset) > 0) { - var c = words[maxLength - offset]; - if(c.match(/\s/)) { - cutPos = maxLength - offset; - break; - } - offset++; + if(words.length < maxLength) { + destination.push(words); + return destination; } - if(maxLength - offset <= 0) { - cutPos = maxLength; + var c = words[maxLength - 1]; + var cutPos; + if(c.match(/\s/)) { + cutPos = maxLength - 1; + } else { + var offset = 1; + while( (maxLength - offset) > 0) { + var c = words[maxLength - offset]; + if(c.match(/\s/)) { + cutPos = maxLength - offset; + break; + } + offset++; + } + if(maxLength - offset <= 0) { + cutPos = maxLength; + } } - } - var part = words.substring(0, cutPos); - destination.push(part); - return this._splitLongLines(words.substring(cutPos + 1, words.length), maxLength, destination); -}; // }}} + var part = words.substring(0, cutPos); + destination.push(part); + return this._splitLongLines(words.substring(cutPos + 1, words.length), maxLength, destination); +} // }}} Client.prototype.say = function(target, text) { // {{{ - this._speak('PRIVMSG', target, text); -}; // }}} + this._speak('PRIVMSG', target, text); +} // }}} Client.prototype.notice = function(target, text) { // {{{ - this._speak('NOTICE', target, text); -}; // }}} + this._speak('NOTICE', target, text); +} // }}} Client.prototype._speak = function(kind, target, text) { // {{{ - var self = this; - var maxLength = this.maxLineLength - target.length; - if (typeof text !== 'undefined') { - text.toString().split(/\r?\n/).filter(function(line) { - return line.length > 0; - }).forEach(function(line) { - var linesToSend = self._splitLongLines(line, maxLength, []); - linesToSend.forEach(function(toSend) { - self.send(kind, target, toSend); - if(kind == 'PRIVMSG') { - self.emit('selfMessage', target, toSend); - } - }); - }); - } -}; // }}} + var self = this; + var maxLength = this.maxLineLength - target.length; + if (typeof text !== 'undefined') { + text.toString().split(/\r?\n/).filter(function(line) { + return line.length > 0; + }).forEach(function(line) { + var linesToSend = self._splitLongLines(line, maxLength, []); + linesToSend.forEach(function(toSend) { + self.send(kind, target, toSend); + if(kind == 'PRIVMSG') { + self.emit('selfMessage', target, toSend); + } + }); + }); + } +} // }}} Client.prototype.whois = function(nick, callback) { // {{{ if (typeof callback === 'function') { var callbackWrapper = function(info) { @@ -949,12 +949,11 @@ Client.prototype.ctcp = function(to, type, text) { }; // blatantly stolen from irssi's splitlong.pl. Thanks, Bjoern Krombholz! -Client.prototype.updateMaxLineLength = function () { +Client.prototype._updateMaxLineLength = function () { // 497 = 510 - (":" + "!" + " PRIVMSG " + " :").length; // target is determined in _speak() and subtracted there this.maxLineLength = 497 - this.nick.length - this.hostMask.length; }; - /* * parseMessage(line, stripColors) * From 0cb93727daff37fad459bc61fd6012ad31a697a0 Mon Sep 17 00:00:00 2001 From: Chris Nehren Date: Tue, 6 Jan 2015 23:29:02 -0500 Subject: [PATCH 3/3] Style changes for lint --- lib/irc.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/irc.js b/lib/irc.js index 51d005c0..d25d2e5e 100644 --- a/lib/irc.js +++ b/lib/irc.js @@ -851,40 +851,40 @@ Client.prototype.action = function(channel, text) { // {{{ self.say(channel, '\u0001ACTION ' + line + '\u0001'); }); } -} // }}} +}; // }}} Client.prototype._splitLongLines = function(words, maxLength, destination) { // {{{ - if(words.length < maxLength) { + if (words.length < maxLength) { destination.push(words); return destination; } var c = words[maxLength - 1]; var cutPos; - if(c.match(/\s/)) { + if (c.match(/\s/)) { cutPos = maxLength - 1; } else { var offset = 1; - while( (maxLength - offset) > 0) { + while ((maxLength - offset) > 0) { var c = words[maxLength - offset]; - if(c.match(/\s/)) { + if (c.match(/\s/)) { cutPos = maxLength - offset; break; } offset++; } - if(maxLength - offset <= 0) { + if (maxLength - offset <= 0) { cutPos = maxLength; } } var part = words.substring(0, cutPos); destination.push(part); return this._splitLongLines(words.substring(cutPos + 1, words.length), maxLength, destination); -} // }}} +}; // }}} Client.prototype.say = function(target, text) { // {{{ this._speak('PRIVMSG', target, text); -} // }}} +}; // }}} Client.prototype.notice = function(target, text) { // {{{ this._speak('NOTICE', target, text); -} // }}} +}; // }}} Client.prototype._speak = function(kind, target, text) { // {{{ var self = this; var maxLength = this.maxLineLength - target.length; @@ -895,13 +895,13 @@ Client.prototype._speak = function(kind, target, text) { // {{{ var linesToSend = self._splitLongLines(line, maxLength, []); linesToSend.forEach(function(toSend) { self.send(kind, target, toSend); - if(kind == 'PRIVMSG') { + if (kind == 'PRIVMSG') { self.emit('selfMessage', target, toSend); } }); }); } -} // }}} +}; // }}} Client.prototype.whois = function(nick, callback) { // {{{ if (typeof callback === 'function') { var callbackWrapper = function(info) { @@ -949,7 +949,7 @@ Client.prototype.ctcp = function(to, type, text) { }; // blatantly stolen from irssi's splitlong.pl. Thanks, Bjoern Krombholz! -Client.prototype._updateMaxLineLength = function () { +Client.prototype._updateMaxLineLength = function() { // 497 = 510 - (":" + "!" + " PRIVMSG " + " :").length; // target is determined in _speak() and subtracted there this.maxLineLength = 497 - this.nick.length - this.hostMask.length;