diff --git a/package.json b/package.json index 28e0e926..35c97db1 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "event-stream": "3.3.2", "express": "4.13.4", "lodash": "4.6.1", + "irc-framework": "1.0.3", "mkdirp": "0.5.1", "moment": "2.12.0", "read": "1.0.7", diff --git a/src/client.js b/src/client.js index 9e0856e0..5196d078 100644 --- a/src/client.js +++ b/src/client.js @@ -3,11 +3,9 @@ var Chan = require("./models/chan"); var crypto = require("crypto"); var identd = require("./identd"); var log = require("./log"); -var net = require("net"); var Msg = require("./models/msg"); var Network = require("./models/network"); -var slate = require("slate-irc"); -var tls = require("tls"); +var ircFramework = require("irc-framework"); var Helper = require("./helper"); module.exports = Client; @@ -25,7 +23,6 @@ var events = [ "link", "names", "nick", - "notice", "part", "quit", "topic", @@ -33,7 +30,6 @@ var events = [ "whois" ]; var inputs = [ - // These inputs are sorted in order that is most likely to be used "msg", "part", "action", @@ -129,97 +125,69 @@ Client.prototype.connect = function(args) { var config = Helper.getConfig(); var client = this; - if (config.lockNetwork) { - // This check is needed to prevent invalid user configurations - if (args.host && args.host.length > 0 && args.host !== config.defaults.host) { - var invalidHostnameMsg = new Msg({ - type: Msg.Type.ERROR, - text: "Hostname you specified is not allowed." - }); - client.emit("msg", { - msg: invalidHostnameMsg - }); - return; - } + var nick = args.nick || "lounge-user"; - args.host = config.defaults.host; - args.port = config.defaults.port; - args.tls = config.defaults.tls; - } - - var server = { + var network = new Network({ name: args.name || "", host: args.host || "", port: parseInt(args.port, 10) || (args.tls ? 6697 : 6667), - rejectUnauthorized: false - }; - - if (server.host.length === 0) { - var emptyHostnameMsg = new Msg({ - type: Msg.Type.ERROR, - text: "You must specify a hostname to connect." - }); - client.emit("msg", { - msg: emptyHostnameMsg - }); - return; - } - - if (config.bind) { - server.localAddress = config.bind; - if (args.tls) { - var socket = net.connect(server); - server.socket = socket; - } - } - - var stream = args.tls ? tls.connect(server) : net.connect(server); - - stream.on("error", function(e) { - console.log("Client#connect():\n" + e); - stream.end(); - var msg = new Msg({ - type: Msg.Type.ERROR, - text: "Connection error." - }); - client.emit("msg", { - msg: msg - }); - }); - - var nick = args.nick || "lounge-user"; - var username = args.username || nick.replace(/[^a-zA-Z0-9]/g, ""); - var realname = args.realname || "The Lounge User"; - - var irc = slate(stream); - identd.hook(stream, username); - - if (args.password) { - irc.pass(args.password); - } - - irc.me = nick; - irc.nick(nick); - irc.user(username, realname); - - var network = new Network({ - name: server.name, - host: server.host, - port: server.port, tls: !!args.tls, password: args.password, - username: username, - realname: realname, + username: args.username || nick.replace(/[^a-zA-Z0-9]/g, ""), + realname: args.realname || "The Lounge User", commands: args.commands }); - network.irc = irc; - client.networks.push(network); client.emit("network", { network: network }); + if (config.lockNetwork) { + // This check is needed to prevent invalid user configurations + if (args.host && args.host.length > 0 && args.host !== config.defaults.host) { + client.emit("msg", { + chan: network.channels[0].id, + msg: new Msg({ + type: Msg.Type.ERROR, + text: "Hostname you specified is not allowed." + }) + }); + return; + } + + network.host = config.defaults.host; + network.port = config.defaults.port; + network.tls = config.defaults.tls; + } + + if (network.host.length === 0) { + client.emit("msg", { + chan: network.channels[0].id, + msg: new Msg({ + type: Msg.Type.ERROR, + text: "You must specify a hostname to connect." + }) + }); + return; + } + + var irc = new ircFramework.Client(); + irc.connect({ + host: network.host, + port: network.port, + nick: nick, + username: network.username, + gecos: network.realname, + password: network.password, + tls: network.tls, + localAddress: config.bind, + rejectUnauthorized: false, + auto_reconnect: false, // TODO: Enable auto reconnection + }); + + network.irc = irc; + events.forEach(function(plugin) { var path = "./plugins/irc-events/" + plugin; require(path).apply(client, [ @@ -228,7 +196,39 @@ Client.prototype.connect = function(args) { ]); }); - irc.once("welcome", function() { + irc.on("raw socket connected", function() { + identd.hook(irc.socket, network.username); + }); + + irc.on("socket connected", function() { + client.emit("msg", { + chan: network.channels[0].id, + msg: new Msg({ + text: "Connected to the network." + }) + }); + }); + + irc.on("socket close", function() { + client.emit("msg", { + chan: network.channels[0].id, + msg: new Msg({ + type: Msg.Type.ERROR, + text: "Disconnected from the network." + }) + }); + }); + + irc.on("reconnecting", function() { + client.emit("msg", { + chan: network.channels[0].id, + msg: new Msg({ + text: "Reconnecting..." + }) + }); + }); + + irc.on("registered", function() { var delay = 1000; var commands = args.commands; if (Array.isArray(commands)) { @@ -242,16 +242,13 @@ Client.prototype.connect = function(args) { delay += 1000; }); } - setTimeout(function() { - irc.write("PING " + network.host); - }, delay); - }); - irc.once("pong", function() { var join = (args.join || ""); if (join) { - join = join.replace(/\,/g, " ").split(/\s+/g); - irc.join(join); + setTimeout(function() { + join = join.replace(/\,/g, " ").split(/\s+/g); + irc.join(join); + }, delay); } }); }; diff --git a/src/models/network.js b/src/models/network.js index 0b641177..5e0200dc 100644 --- a/src/models/network.js +++ b/src/models/network.js @@ -30,7 +30,7 @@ function Network(attr) { } Network.prototype.toJSON = function() { - var json = _.extend(this, {nick: (this.irc || {}).me || ""}); + var json = _.extend(this, {nick: (this.irc && this.irc.user.nick) || ""}); return _.omit(json, "irc", "password"); }; @@ -45,7 +45,7 @@ Network.prototype.export = function() { "realname", "commands" ]); - network.nick = (this.irc || {}).me; + network.nick = (this.irc && this.irc.user.nick) || ""; network.join = _.map( _.filter(this.channels, {type: "channel"}), "name" diff --git a/src/models/user.js b/src/models/user.js index 755e53dd..79c8947d 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -3,6 +3,10 @@ var _ = require("lodash"); module.exports = User; function User(attr) { + // TODO: Remove this + attr.name = attr.nick; + attr.mode = attr.modes[0] || ""; + _.merge(this, _.extend({ mode: "", name: "" diff --git a/src/plugins/inputs/action.js b/src/plugins/inputs/action.js index bdb85d25..cf4b753a 100644 --- a/src/plugins/inputs/action.js +++ b/src/plugins/inputs/action.js @@ -18,7 +18,7 @@ exports.input = function(network, chan, cmd, args) { text ); irc.emit("message", { - from: irc.me, + from: irc.user.nick, to: chan.name, message: "\u0001ACTION " + text }); diff --git a/src/plugins/inputs/msg.js b/src/plugins/inputs/msg.js index 27a27db3..da3edeb3 100644 --- a/src/plugins/inputs/msg.js +++ b/src/plugins/inputs/msg.js @@ -21,7 +21,7 @@ exports.input = function(network, chan, cmd, args) { var channel = _.find(network.channels, {name: target}); if (typeof channel !== "undefined") { irc.emit("message", { - from: irc.me, + from: irc.user.nick, to: channel.name, message: msg }); diff --git a/src/plugins/inputs/notice.js b/src/plugins/inputs/notice.js index 4c5981e5..09445e5c 100644 --- a/src/plugins/inputs/notice.js +++ b/src/plugins/inputs/notice.js @@ -20,8 +20,8 @@ exports.input = function(network, chan, cmd, args) { var msg = new Msg({ type: Msg.Type.NOTICE, - mode: targetChan.getMode(irc.me), - from: irc.me, + mode: targetChan.getMode(irc.user.nick), + from: irc.user.nick, text: message }); targetChan.messages.push(msg); diff --git a/src/plugins/irc-events/ctcp.js b/src/plugins/irc-events/ctcp.js index 42b7fed8..e329a4d3 100644 --- a/src/plugins/irc-events/ctcp.js +++ b/src/plugins/irc-events/ctcp.js @@ -1,22 +1,15 @@ var pkg = require(process.cwd() + "/package.json"); module.exports = function(irc/* , network */) { - irc.on("message", function(data) { - if (data.message.indexOf("\001") !== 0) { - return; - } - var msg = data.message.replace(/\001/g, ""); - var split = msg.split(" "); - switch (split[0]) { + irc.on("ctcp request", function(data) { + switch (data.type) { case "VERSION": - irc.ctcp( - data.from, - "VERSION " + pkg.name + " " + pkg.version - ); + irc.ctcpResponse(data.nick, "VERSION " + pkg.name + " " + pkg.version); break; case "PING": + var split = data.msg.split(" "); if (split.length === 2) { - irc.ctcp(data.from, "PING " + split[1]); + irc.ctcpResponse(data.nick, "PING " + split[1]); } break; } diff --git a/src/plugins/irc-events/error.js b/src/plugins/irc-events/error.js index c56a36cc..020ceeef 100644 --- a/src/plugins/irc-events/error.js +++ b/src/plugins/irc-events/error.js @@ -14,7 +14,7 @@ module.exports = function(irc, network) { }); if (!network.connected) { if (data.cmd === "ERR_NICKNAMEINUSE") { - var random = irc.me + Math.floor(10 + (Math.random() * 89)); + var random = irc.user.nick + Math.floor(10 + (Math.random() * 89)); irc.nick(random); } } diff --git a/src/plugins/irc-events/invite.js b/src/plugins/irc-events/invite.js index 6bf1e3b7..cc3a4229 100644 --- a/src/plugins/irc-events/invite.js +++ b/src/plugins/irc-events/invite.js @@ -16,7 +16,7 @@ module.exports = function(irc, network) { from: data.from, target: target, text: data.channel, - invitedYou: target.toLowerCase() === irc.me.toLowerCase() + invitedYou: target === irc.user.nick }); chan.messages.push(msg); client.emit("msg", { diff --git a/src/plugins/irc-events/join.js b/src/plugins/irc-events/join.js index 07e0bd0f..4410e9c3 100644 --- a/src/plugins/irc-events/join.js +++ b/src/plugins/irc-events/join.js @@ -18,20 +18,16 @@ module.exports = function(irc, network) { chan: chan }); } - chan.users.push(new User({name: data.nick})); + chan.users.push(new User({nick: data.nick, modes: ""})); chan.sortUsers(); client.emit("users", { chan: chan.id }); - var self = false; - if (data.nick.toLowerCase() === irc.me.toLowerCase()) { - self = true; - } var msg = new Msg({ from: data.nick, - hostmask: data.hostmask.username + "@" + data.hostmask.hostname, + hostmask: data.ident + "@" + data.hostname, type: Msg.Type.JOIN, - self: self + self: data.nick === irc.user.nick }); chan.messages.push(msg); client.emit("msg", { diff --git a/src/plugins/irc-events/kick.js b/src/plugins/irc-events/kick.js index c5bac10d..9e456edb 100644 --- a/src/plugins/irc-events/kick.js +++ b/src/plugins/irc-events/kick.js @@ -12,7 +12,7 @@ module.exports = function(irc, network) { return; } - if (data.client === irc.me) { + if (data.client === irc.user.nick) { chan.users = []; } else { chan.users = _.without(chan.users, _.find(chan.users, {name: data.client})); @@ -22,17 +22,13 @@ module.exports = function(irc, network) { chan: chan.id }); - var self = false; - if (data.nick.toLowerCase() === irc.me.toLowerCase()) { - self = true; - } var msg = new Msg({ type: Msg.Type.KICK, mode: mode, from: from, target: data.client, text: data.message || "", - self: self + self: data.nick === irc.user.nick }); chan.messages.push(msg); client.emit("msg", { diff --git a/src/plugins/irc-events/link.js b/src/plugins/irc-events/link.js index d5317016..37d4e72a 100644 --- a/src/plugins/irc-events/link.js +++ b/src/plugins/irc-events/link.js @@ -27,7 +27,7 @@ module.exports = function(irc, network) { return; } - var self = data.to.toLowerCase() === irc.me.toLowerCase(); + var self = data.to.toLowerCase() === irc.user.nick.toLowerCase(); var chan = _.find(network.channels, {name: self ? data.from : data.to}); if (typeof chan === "undefined") { return; diff --git a/src/plugins/irc-events/message.js b/src/plugins/irc-events/message.js index 0894843b..3ac43c86 100644 --- a/src/plugins/irc-events/message.js +++ b/src/plugins/irc-events/message.js @@ -7,22 +7,32 @@ module.exports = function(irc, network) { var client = this; var config = Helper.getConfig(); - irc.on("message", function(data) { - if (data.message.indexOf("\u0001") === 0 && data.message.substring(0, 7) !== "\u0001ACTION") { - // Hide ctcp messages. - return; - } + irc.on("notice", function(data) { + data.type = Msg.Type.NOTICE; + handleMessage(data); + }); - var target = data.to; - if (target.toLowerCase() === irc.me.toLowerCase()) { - target = data.from; + irc.on("action", function(data) { + data.type = Msg.Type.ACTION; + handleMessage(data); + }); + + irc.on("privmsg", function(data) { + data.type = Msg.Type.MESSAGE; + handleMessage(data); + }); + + function handleMessage(data) { + var target = data.target; + if (target.toLowerCase() === irc.user.nick.toLowerCase()) { + target = data.nick; } var chan = _.find(network.channels, {name: target}); if (typeof chan === "undefined") { chan = new Chan({ type: Chan.Type.QUERY, - name: data.from + name: data.nick }); network.channels.push(chan); client.emit("join", { @@ -31,19 +41,11 @@ module.exports = function(irc, network) { }); } - var type = Msg.Type.MESSAGE; - var text = data.message; - var textSplit = text.split(" "); - if (textSplit[0] === "\u0001ACTION") { - type = Msg.Type.ACTION; - text = text.replace(/^\u0001ACTION|\u0001$/g, ""); - } - - var self = (data.from.toLowerCase() === irc.me.toLowerCase()); + var self = data.nick === irc.user.nick; // Self messages are never highlighted // Non-self messages are highlighted as soon as the nick is detected - var highlight = !self && textSplit.some(function(w) { + var highlight = !self && data.msg.split(" ").some(function(w) { return (w.replace(/^@/, "").toLowerCase().indexOf(irc.me.toLowerCase()) === 0); }); @@ -55,12 +57,11 @@ module.exports = function(irc, network) { } } - var name = data.from; var msg = new Msg({ - type: type, - mode: chan.getMode(name), - from: name, - text: text, + type: data.type, + mode: chan.getMode(data.nick), + from: data.nick, + text: data.msg, self: self, highlight: highlight }); @@ -74,5 +75,5 @@ module.exports = function(irc, network) { chan: chan.id, msg: msg }); - }); + } }; diff --git a/src/plugins/irc-events/mode.js b/src/plugins/irc-events/mode.js index be4868eb..f6ca2a21 100644 --- a/src/plugins/irc-events/mode.js +++ b/src/plugins/irc-events/mode.js @@ -13,16 +13,12 @@ module.exports = function(irc, network) { if (from.indexOf(".") !== -1) { from = data.target; } - var self = false; - if (from.toLowerCase() === irc.me.toLowerCase()) { - self = true; - } var msg = new Msg({ type: Msg.Type.MODE, mode: chan.getMode(from), from: from, text: data.mode + " " + (data.client || ""), - self: self + self: from === irc.user.nick }); chan.messages.push(msg); client.emit("msg", { diff --git a/src/plugins/irc-events/motd.js b/src/plugins/irc-events/motd.js index 3f7d078a..bb5cd141 100644 --- a/src/plugins/irc-events/motd.js +++ b/src/plugins/irc-events/motd.js @@ -4,16 +4,31 @@ module.exports = function(irc, network) { var client = this; irc.on("motd", function(data) { var lobby = network.channels[0]; - data.motd.forEach(function(text) { + + if (data.motd) { + data.motd.split("\n").forEach(function(text) { + var msg = new Msg({ + type: Msg.Type.MOTD, + text: text + }); + lobby.messages.push(msg); + client.emit("msg", { + chan: lobby.id, + msg: msg + }); + }); + } + + if (data.error) { var msg = new Msg({ type: Msg.Type.MOTD, - text: text + text: data.error }); lobby.messages.push(msg); client.emit("msg", { chan: lobby.id, msg: msg }); - }); + } }); }; diff --git a/src/plugins/irc-events/names.js b/src/plugins/irc-events/names.js index 5f4f7aa9..5612b09f 100644 --- a/src/plugins/irc-events/names.js +++ b/src/plugins/irc-events/names.js @@ -3,13 +3,13 @@ var User = require("../../models/user"); module.exports = function(irc, network) { var client = this; - irc.on("names", function(data) { + irc.on("userlist", function(data) { var chan = _.find(network.channels, {name: data.channel}); if (typeof chan === "undefined") { return; } chan.users = []; - _.each(data.names, function(u) { + _.each(data.users, function(u) { chan.users.push(new User(u)); }); chan.sortUsers(); diff --git a/src/plugins/irc-events/nick.js b/src/plugins/irc-events/nick.js index a77d12a3..ca51bbde 100644 --- a/src/plugins/irc-events/nick.js +++ b/src/plugins/irc-events/nick.js @@ -6,7 +6,7 @@ module.exports = function(irc, network) { irc.on("nick", function(data) { var self = false; var nick = data["new"]; - if (nick === irc.me) { + if (nick === irc.user.nick) { var lobby = network.channels[0]; var msg = new Msg({ text: "You're now known as " + nick, diff --git a/src/plugins/irc-events/part.js b/src/plugins/irc-events/part.js index 89d2364e..2c0600d4 100644 --- a/src/plugins/irc-events/part.js +++ b/src/plugins/irc-events/part.js @@ -9,7 +9,7 @@ module.exports = function(irc, network) { return; } var from = data.nick; - if (from === irc.me) { + if (from === irc.user.nick) { network.channels = _.without(network.channels, chan); client.save(); client.emit("part", { diff --git a/src/plugins/irc-events/topic.js b/src/plugins/irc-events/topic.js index 600780fe..9c746e5b 100644 --- a/src/plugins/irc-events/topic.js +++ b/src/plugins/irc-events/topic.js @@ -17,7 +17,7 @@ module.exports = function(irc, network) { from: from, text: topic, isSetByChan: from === chan.name, - self: (from.toLowerCase() === irc.me.toLowerCase()) + self: from === irc.user.nick }); chan.messages.push(msg); client.emit("msg", { diff --git a/src/plugins/irc-events/welcome.js b/src/plugins/irc-events/welcome.js index 9bac7c6c..23c5f54f 100644 --- a/src/plugins/irc-events/welcome.js +++ b/src/plugins/irc-events/welcome.js @@ -2,13 +2,12 @@ var Msg = require("../../models/msg"); module.exports = function(irc, network) { var client = this; - irc.on("welcome", function(data) { + irc.on("registered", function(data) { network.connected = true; - irc.write("PING " + network.host); + network.nick = data.nick; var lobby = network.channels[0]; - var nick = data; var msg = new Msg({ - text: "You're now known as " + nick + text: "You're now known as " + data.nick }); lobby.messages.push(msg); client.emit("msg", { @@ -18,7 +17,7 @@ module.exports = function(irc, network) { client.save(); client.emit("nick", { network: network.id, - nick: nick + nick: data.nick }); }); };