799 lines
15 KiB
JavaScript
799 lines
15 KiB
JavaScript
var _ = require("lodash");
|
|
var config = require("../config") || {};
|
|
var fs = require("fs");
|
|
var http = require("connect");
|
|
var io = require("socket.io");
|
|
var irc = require("slate-irc");
|
|
var net = require("net");
|
|
var tls = require("tls");
|
|
|
|
// Models
|
|
|
|
var Chan = require("./models/chan");
|
|
var Msg = require("./models/msg");
|
|
var Network = require("./models/network");
|
|
var User = require("./models/user");
|
|
|
|
var sockets = null;
|
|
var networks = [];
|
|
|
|
var events = [
|
|
"errors",
|
|
"join",
|
|
"kick",
|
|
"mode",
|
|
"motd",
|
|
"message",
|
|
"names",
|
|
"nick",
|
|
"notice",
|
|
"part",
|
|
"quit",
|
|
"topic",
|
|
"welcome",
|
|
"whois",
|
|
];
|
|
|
|
module.exports = listen;
|
|
|
|
function listen() {
|
|
var port = config.port || 9000;
|
|
var app = http()
|
|
.use(index)
|
|
.use(http.static("client"))
|
|
.listen(port);
|
|
|
|
sockets = io.listen(app, {log: 0}).sockets.on("connection", function(socket) {
|
|
init.call(
|
|
socket,
|
|
!!config.password
|
|
);
|
|
});
|
|
|
|
(config.networks || []).forEach(function(n) {
|
|
connect(n);
|
|
});
|
|
}
|
|
|
|
function init(login) {
|
|
if (login) {
|
|
this.on("auth", auth);
|
|
this.emit("auth");
|
|
} else {
|
|
this.join("chat");
|
|
this.on("debug", debug);
|
|
this.on("input", input);
|
|
this.on("fetch", fetch);
|
|
this.emit(
|
|
"networks",
|
|
{networks: networks}
|
|
);
|
|
}
|
|
}
|
|
|
|
function index(req, res, next) {
|
|
if (req.url != "/") return next();
|
|
fs.readFile("client/index.html", function(err, file) {
|
|
var data = _.merge(
|
|
require("../package.json"),
|
|
config
|
|
);
|
|
res.end(_.template(
|
|
file,
|
|
data
|
|
));
|
|
});
|
|
}
|
|
|
|
function connect(params) {
|
|
_.defaults(
|
|
params,
|
|
config.defaults
|
|
);
|
|
|
|
var host = params.host;
|
|
var port = params.port || 6667;
|
|
var options = {
|
|
host: host,
|
|
port: port,
|
|
};
|
|
|
|
var stream = params.tls ? tls.connect(options) : net.connect(options);
|
|
|
|
stream.on("error", function(e) {
|
|
console.log(e);
|
|
});
|
|
|
|
var client = irc(stream);
|
|
var network = new Network({
|
|
host: host,
|
|
client: client,
|
|
});
|
|
|
|
networks.push(network);
|
|
sockets.in("chat").emit("networks", {networks: networks});
|
|
|
|
client.nick(params.nick);
|
|
client.user(params.nick, params.realname);
|
|
|
|
client.once("welcome", function() {
|
|
(params.onConnect.join || []).forEach(function(chan) {
|
|
client.join.apply(
|
|
client,
|
|
chan.split(' ')
|
|
);
|
|
});
|
|
|
|
// Trigger 'PONG' response.
|
|
client.write("PING " + network.host);
|
|
});
|
|
|
|
client.once("pong", function() {
|
|
var delay = 1000;
|
|
(params.onConnect.commands || []).forEach(function(cmd) {
|
|
setTimeout(function() {
|
|
input({
|
|
id: network.channels[0].id,
|
|
text: cmd
|
|
});
|
|
}, delay);
|
|
delay += 1000;
|
|
});
|
|
});
|
|
|
|
events.forEach(function(e) {
|
|
client.on(e, function() {
|
|
event.apply(network, [e, arguments]);
|
|
});
|
|
});
|
|
}
|
|
|
|
function auth(password) {
|
|
if (password == config.password) {
|
|
this.disable = false;
|
|
init.call(this);
|
|
}
|
|
}
|
|
|
|
function debug(data) {
|
|
console.log(data);
|
|
}
|
|
|
|
function input(data) {
|
|
var target = find(data.id);
|
|
if (!target) {
|
|
return;
|
|
}
|
|
|
|
var network = target.network;
|
|
var chan = target.chan;
|
|
|
|
var client = network.client;
|
|
|
|
var id = data.id;
|
|
var text = data.text;
|
|
if (!text) {
|
|
return;
|
|
}
|
|
|
|
var args = text.replace(/^\//, '').split(" ");
|
|
var cmd = text.charAt(0) == "/" ? args[0].toLowerCase() : "";
|
|
|
|
switch (cmd) {
|
|
case "say":
|
|
// Remove '/say' and treat this command as a message.
|
|
args.shift();
|
|
case "":
|
|
args.unshift(
|
|
"msg",
|
|
chan.name
|
|
);
|
|
case "msg":
|
|
var user;
|
|
var text = args.slice(2).join(" ");
|
|
if (client) {
|
|
user = client.me;
|
|
client.send(args[1], text);
|
|
}
|
|
var chan = _.findWhere(network.channels, {name: args[1]});
|
|
if (typeof chan !== "undefined") {
|
|
var msg = new Msg({
|
|
from: user,
|
|
text: text,
|
|
});
|
|
chan.addMsg(msg)
|
|
sockets.in("chat").emit("msg", {
|
|
id: chan.id,
|
|
msg: msg,
|
|
});
|
|
}
|
|
break;
|
|
|
|
case "notice":
|
|
if (client && args[2]) {
|
|
client.notice(args[1], args.slice(2).join(" "));
|
|
}
|
|
break;
|
|
|
|
case "slap":
|
|
var slap = "slaps " + args[1] + " around a bit with a large trout";
|
|
case "me":
|
|
if (!args[1]) {
|
|
break;
|
|
}
|
|
var user;
|
|
var text = slap || args.slice(1).join(" ");
|
|
if (client) {
|
|
user = client.me;
|
|
client.action(chan.name, text);
|
|
}
|
|
var msg = new Msg({
|
|
type: "action",
|
|
from: user,
|
|
text: text,
|
|
});
|
|
chan.addMsg(msg)
|
|
sockets.in("chat").emit("msg", {
|
|
id: chan.id,
|
|
msg: msg,
|
|
});
|
|
break;
|
|
|
|
case "ame":
|
|
var type = "action";
|
|
case "amsg":
|
|
var user = client.me;
|
|
var text = args.slice(1).join(" ");
|
|
var channels = [];
|
|
network.channels.forEach(function(chan) {
|
|
if (chan.type == "channel") {
|
|
channels.push(chan.name);
|
|
var msg = new Msg({
|
|
type: type || "normal",
|
|
from: user,
|
|
text: text,
|
|
});
|
|
chan.addMsg(msg)
|
|
sockets.in("chat").emit("msg", {
|
|
id: chan.id,
|
|
msg: msg,
|
|
});
|
|
}
|
|
});
|
|
client[type || "send"](
|
|
channels,
|
|
text
|
|
);
|
|
break;
|
|
|
|
case "server":
|
|
case "connect":
|
|
if (args[1]) {
|
|
connect({host: args[1]});
|
|
}
|
|
break;
|
|
|
|
case "join":
|
|
if (client && args[1]) {
|
|
if (!args[2] || args[2].charAt(0) == "#") {
|
|
client.join(args.slice(1));
|
|
} else {
|
|
client.join(
|
|
args[1],
|
|
args[2] // Password
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "nick":
|
|
if (client && args[1]) {
|
|
client.nick(args[1]);
|
|
}
|
|
break;
|
|
|
|
case "part":
|
|
if (chan.type != "channel") {
|
|
return;
|
|
}
|
|
case "close":
|
|
case "leave":
|
|
if (chan.type == "lobby") {
|
|
return;
|
|
}
|
|
var id = chan.id;
|
|
if (chan.type == "query" || !chan.users.length) {
|
|
remove(id);
|
|
sockets.in("chat").emit("part", {
|
|
id: id,
|
|
});
|
|
} else if (client) {
|
|
client.part(chan.name);
|
|
}
|
|
break;
|
|
|
|
case "partall":
|
|
var part = [];
|
|
network.channels.forEach(function(c) {
|
|
if (c.type == "channel") part.push(c.name);
|
|
});
|
|
console.log("PART");
|
|
console.log(part);
|
|
client.part(part);
|
|
break;
|
|
|
|
case "invite":
|
|
if (client && args[2]) {
|
|
client.invite(args[1], args[2]);
|
|
}
|
|
break;
|
|
|
|
case "topic":
|
|
if (client) {
|
|
var msg = "TOPIC";
|
|
msg += " " + chan.name;
|
|
msg += args[1] ? " :" + args.slice(1).join(" ") : "";
|
|
client.write(msg);
|
|
}
|
|
break;
|
|
|
|
case "whoami":
|
|
var user = client.me;
|
|
case "query":
|
|
case "whois":
|
|
var user = user || args[1];
|
|
if (client && user) {
|
|
client.whois(user);
|
|
}
|
|
break;
|
|
|
|
case "kick":
|
|
if (client && args[1]) {
|
|
client.kick(chan.name, args[1]);
|
|
}
|
|
break;
|
|
|
|
case "op":
|
|
case "deop":
|
|
case "voice":
|
|
case "devoice":
|
|
case "mode":
|
|
if (!client || !args[1]) {
|
|
break;
|
|
}
|
|
var mode;
|
|
var user;
|
|
if (cmd != "mode") {
|
|
user = args[1];
|
|
mode = {
|
|
"op": "+o",
|
|
"deop": "-o",
|
|
"voice": "+v",
|
|
"devoice": "-v",
|
|
}[cmd];
|
|
} else if (!args[2]) {
|
|
break;
|
|
} else {
|
|
mode = args[1];
|
|
user = args[2];
|
|
}
|
|
client.mode(
|
|
chan.name,
|
|
mode,
|
|
user
|
|
);
|
|
break;
|
|
|
|
case "quit":
|
|
case "disconnect":
|
|
if (client) {
|
|
networks = _.without(networks, network);
|
|
sockets.in("chat").emit("networks", {networks: networks});
|
|
client.quit();
|
|
}
|
|
break;
|
|
|
|
// Send raw IRC messages.
|
|
case "raw":
|
|
case "send":
|
|
if (client) {
|
|
client.write(args.slice(1).join(" "));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
function fetch(data) {
|
|
var socket = this;
|
|
var target = find(data.id);
|
|
if (!target) {
|
|
return;
|
|
}
|
|
var chan = target.chan;
|
|
var messages = chan
|
|
.messages
|
|
.slice(0, chan.messages.length - (data.count || 0));
|
|
socket.emit("messages", {
|
|
id: data.id,
|
|
msg: messages,
|
|
});
|
|
}
|
|
|
|
function event(e, data) {
|
|
var data = _.last(data);
|
|
var network = this;
|
|
|
|
switch (e) {
|
|
case "errors":
|
|
sockets.in("chat").emit("msg", {
|
|
msg: new Msg({
|
|
type: "error",
|
|
from: "-!-",
|
|
text: data.message,
|
|
}),
|
|
});
|
|
if (!this.connected) {
|
|
if (data.cmd == "ERR_NICKNAMEINUSE") {
|
|
var random = config.defaults.nick + Math.floor(10 + (Math.random() * 89));
|
|
this.client.nick(random);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "join":
|
|
var chan = _.findWhere(this.channels, {name: data.channel});
|
|
if (typeof chan === "undefined") {
|
|
chan = new Chan({
|
|
name: data.channel,
|
|
});
|
|
this.addChan(chan);
|
|
sockets.in("chat").emit("join", {
|
|
id: this.id,
|
|
chan: chan,
|
|
});
|
|
}
|
|
var users = chan.users;
|
|
users.push(new User({name: data.nick}));
|
|
chan.sortUsers();
|
|
sockets.in("chat").emit("users", {
|
|
id: chan.id,
|
|
users: users,
|
|
});
|
|
var msg = new Msg({
|
|
from: data.nick,
|
|
type: "join",
|
|
});
|
|
chan.addMsg(msg);
|
|
sockets.in("chat").emit("msg", {
|
|
id: chan.id,
|
|
msg: msg,
|
|
});
|
|
break;
|
|
|
|
case "kick":
|
|
var chan = _.findWhere(this.channels, {name: data.channel});
|
|
if (typeof chan === "undefined") {
|
|
break;
|
|
}
|
|
if (data.client == this.client.me) {
|
|
chan.users = [];
|
|
} else {
|
|
chan.users = _.without(chan.users, _.findWhere(chan.users, {name: data.client}));
|
|
}
|
|
sockets.in("chat").emit("users", {
|
|
id: chan.id,
|
|
users: chan.users,
|
|
});
|
|
var msg = new Msg({
|
|
type: "kick",
|
|
from: data.nick,
|
|
text: data.client,
|
|
});
|
|
chan.addMsg(msg);
|
|
sockets.in("chat").emit("msg", {
|
|
id: chan.id,
|
|
msg: msg,
|
|
});
|
|
break;
|
|
|
|
case "mode":
|
|
var chan = _.findWhere(this.channels, {name: data.target});
|
|
if (typeof chan !== "undefined") {
|
|
clearTimeout(this.timer);
|
|
this.timer = setTimeout((function() {
|
|
this.client.write("NAMES " + data.target);
|
|
}).bind(this), 200);
|
|
var nick = data.nick;
|
|
if (nick.indexOf(".") !== -1) {
|
|
nick = data.target;
|
|
}
|
|
var msg = new Msg({
|
|
type: "mode",
|
|
from: nick,
|
|
text: data.mode + " " + data.client,
|
|
});
|
|
chan.addMsg(msg);
|
|
sockets.in("chat").emit("msg", {
|
|
id: chan.id,
|
|
msg: msg,
|
|
});
|
|
}
|
|
break;
|
|
|
|
case "motd":
|
|
var chan = this.channels[0];
|
|
data.motd.forEach(function(m) {
|
|
var msg = new Msg({
|
|
type: "motd",
|
|
from: "-!-",
|
|
text: m,
|
|
});
|
|
chan.addMsg(msg);
|
|
sockets.in("chat").emit("msg", {
|
|
id: chan.id,
|
|
msg: msg,
|
|
});
|
|
});
|
|
break;
|
|
|
|
case "message":
|
|
var target = data.to;
|
|
var chan = _.findWhere(this.channels, {name: target.charAt(0) == "#" ? target : data.from});
|
|
if (typeof chan === "undefined") {
|
|
chan = new Chan({
|
|
name: data.from,
|
|
type: "query",
|
|
});
|
|
this.addChan(chan);
|
|
sockets.in("chat").emit("join", {
|
|
id: this.id,
|
|
chan: chan,
|
|
});
|
|
}
|
|
var type = "";
|
|
var text = data.message;
|
|
if (text.split(" ")[0] === "\u0001ACTION") {
|
|
type = "action";
|
|
text = text.replace(/\u0001|ACTION/g, "");
|
|
}
|
|
var network = this;
|
|
text.split(' ').forEach(function(w) {
|
|
if (w.indexOf(network.client.me) == 0) type += " highlight";
|
|
});
|
|
var msg = new Msg({
|
|
type: type || "normal",
|
|
from: data.from,
|
|
text: text,
|
|
});
|
|
chan.addMsg(msg);
|
|
sockets.in("chat").emit("msg", {
|
|
id: chan.id,
|
|
msg: msg,
|
|
});
|
|
break;
|
|
|
|
case "names":
|
|
var chan = _.findWhere(this.channels, {name: data.channel});
|
|
if (typeof chan === "undefined") {
|
|
break;
|
|
}
|
|
chan.users = [];
|
|
_.each(data.names, function(n) {
|
|
chan.addUser(new User(n));
|
|
});
|
|
chan.sortUsers();
|
|
sockets.in("chat").emit("users", {
|
|
id: chan.id,
|
|
users: chan.users,
|
|
});
|
|
break;
|
|
|
|
case "nick":
|
|
if (data["new"] == this.client.me) {
|
|
var chan = this.channels[0];
|
|
var msg = new Msg({
|
|
from: "-!-",
|
|
text: "You're now known as " + data["new"],
|
|
});
|
|
chan.addMsg(msg);
|
|
sockets.in("chat").emit("msg", {
|
|
id: chan.id,
|
|
msg: msg,
|
|
});
|
|
}
|
|
this.channels.forEach(function(chan) {
|
|
var user = _.findWhere(chan.users, {name: data.nick});
|
|
if (!user) {
|
|
return;
|
|
}
|
|
user.name = data["new"];
|
|
chan.sortUsers();
|
|
sockets.in("chat").emit("users", {
|
|
id: chan.id,
|
|
users: chan.users,
|
|
});
|
|
var msg = new Msg({
|
|
type: "nick",
|
|
from: data.nick,
|
|
text: data["new"],
|
|
});
|
|
chan.addMsg(msg);
|
|
sockets.in("chat").emit("msg", {
|
|
id: chan.id,
|
|
msg: msg,
|
|
});
|
|
});
|
|
break;
|
|
|
|
case "notice":
|
|
var chan = this.channels[0];
|
|
var from = data.from || "-!-";
|
|
if (data.to == "*" || data.from.indexOf(".") !== -1) {
|
|
from = "-!-";
|
|
}
|
|
var msg = new Msg({
|
|
type: "notice",
|
|
from: from,
|
|
text: data.message,
|
|
});
|
|
chan.addMsg(msg);
|
|
sockets.in("chat").emit("msg", {
|
|
id: chan.id,
|
|
msg: msg,
|
|
});
|
|
break;
|
|
|
|
case "part":
|
|
var chan = _.findWhere(this.channels, {name: data.channels[0]});
|
|
if (typeof chan === "undefined") {
|
|
break;
|
|
}
|
|
if (data.nick == this.client.me) {
|
|
remove(chan.id);
|
|
sockets.in("chat").emit("part", {
|
|
id: chan.id,
|
|
});
|
|
} else {
|
|
chan.users = _.without(chan.users, _.findWhere(chan.users, {name: data.nick}));
|
|
sockets.in("chat").emit("users", {
|
|
id: chan.id,
|
|
users: chan.users,
|
|
});
|
|
var msg = new Msg({
|
|
type: "part",
|
|
from: data.nick,
|
|
});
|
|
chan.addMsg(msg);
|
|
sockets.in("chat").emit("msg", {
|
|
id: chan.id,
|
|
msg: msg,
|
|
});
|
|
}
|
|
break;
|
|
|
|
case "quit":
|
|
this.channels.forEach(function(chan) {
|
|
var user = _.findWhere(chan.users, {name: data.nick});
|
|
if (!user) {
|
|
return;
|
|
}
|
|
chan.users = _.without(chan.users, user);
|
|
sockets.in("chat").emit("users", {
|
|
id: chan.id,
|
|
users: chan.users,
|
|
});
|
|
var msg = new Msg({
|
|
type: "quit",
|
|
from: data.nick,
|
|
});
|
|
chan.addMsg(msg);
|
|
sockets.in("chat").emit("msg", {
|
|
id: chan.id,
|
|
msg: msg,
|
|
});
|
|
});
|
|
break;
|
|
|
|
case "topic":
|
|
var chan = _.findWhere(this.channels, {name: data.channel});
|
|
if (typeof chan === "undefined") {
|
|
break;
|
|
}
|
|
var from = data.nick || chan.name;
|
|
var msg = new Msg({
|
|
type: "topic",
|
|
from: from,
|
|
text: data.topic,
|
|
});
|
|
chan.addMsg(msg);
|
|
sockets.in("chat").emit("msg", {
|
|
id: chan.id,
|
|
msg: msg,
|
|
});
|
|
break;
|
|
|
|
case "welcome":
|
|
this.connected = true;
|
|
var chan = this.channels[0];
|
|
var msg = new Msg({
|
|
from: "-!-",
|
|
text: "You're now known as " + data,
|
|
});
|
|
chan.addMsg(msg);
|
|
sockets.in("chat").emit("msg", {
|
|
id: chan.id,
|
|
msg: msg,
|
|
});
|
|
break;
|
|
|
|
case "whois":
|
|
if (!data) {
|
|
break;
|
|
}
|
|
var chan = _.findWhere(this.channels, {name: data.nickname});
|
|
if (typeof chan === "undefined") {
|
|
chan = new Chan({
|
|
type: "query",
|
|
name: data.nickname,
|
|
});
|
|
this.addChan(chan);
|
|
sockets.in("chat").emit("join", {
|
|
id: this.id,
|
|
chan: chan,
|
|
});
|
|
}
|
|
var prefix = {
|
|
hostname: "from",
|
|
realname: "is",
|
|
channels: "on",
|
|
server: "using",
|
|
};
|
|
var i = 0;
|
|
for (var k in data) {
|
|
var key = prefix[k];
|
|
if (!key || data[k].toString() == "") {
|
|
continue;
|
|
}
|
|
var msg = new Msg({
|
|
type: "whois",
|
|
from: data.nickname,
|
|
text: key + " " + data[k],
|
|
});
|
|
chan.addMsg(msg);
|
|
sockets.in("chat").emit("msg", {
|
|
id: chan.id,
|
|
msg: msg,
|
|
});
|
|
}
|
|
var msg = new Msg({
|
|
type: "whois",
|
|
from: data.nickname,
|
|
text: "End of /WHOIS list.",
|
|
});
|
|
chan.addMsg(msg);
|
|
sockets.in("chat").emit("msg", {
|
|
id: chan.id,
|
|
msg: msg,
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
|
|
function find(id) {
|
|
for (var i = 0; i < networks.length; i++) {
|
|
var result = {
|
|
network: networks[i],
|
|
chan: _.findWhere(networks[i].channels, {id: id}),
|
|
};
|
|
if (result.chan) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
function remove(id) {
|
|
networks.forEach(function(n) {
|
|
n.channels = _.without(n.channels, _.findWhere(n.channels, {id: id}));
|
|
});
|
|
}
|