tennu
Version:
Tennu - Modular IRC Bot Framework for Node.js
394 lines (335 loc) • 13.5 kB
JavaScript
var RFCMessage = require("irc-message");
var mircColors = /\u0003\d?\d?,?\d?\d?/g;
var util = require("util");
// NOTE(Havvy): All numerics are listed at https://www.alien.net.au/irc/irc2numerics.html
var extensions = {
join: function (message) {
message.channel = message.params[0].toLowerCase();
},
part: function (message) {
message.channel = message.params[0].toLowerCase();
message.reason = message.params[1];
},
kick: function (message) {
message.channel = message.params[0].toLowerCase();
message.kicked = message.params[1];
message.reason = message.params[2];
message.kicker = message.nickname;
},
quit: function (message) {
message.reason = message.params[0];
},
privmsg: function (message) {
// NOTE(Havvy): Test fails if new channel prefixes are created by IRCds, but that's unlikely.
message.isQuery = ("#!+.~".indexOf(message.params[0][0]) === -1);
message.message = message.params[1].trim().replace(mircColors, "");
if (message.isQuery) {
message.channel = message.nickname;
} else {
message.channel = message.params[0];
}
},
notice: function (message) {
// Test fails if new channel prefixes are created by IRCds, but that's unlikely.
message.isQuery = ("#!+.~".indexOf(message.params[0][0]) === -1);
message.message = message.params[1].replace(mircColors, "").trim();
if (message.isQuery) {
message.channel = message.nickname;
} else {
message.channel = message.params[0];
}
},
topic: function (message) {
message.channel = message.params[0];
message.topic = message.params[1];
},
nick: function (message) {
message.old = message.hostmask.nickname;
message.new = message.params[0];
},
mode: function (message) {
message.channel = message.params[0]; // Channel or nick
message.modestring = message.params[1]; // +asd-asdaf
// The IRCd should only send the modestring with the argument modes last.
// i.e "+soh nick nick" or "+s-zh+o nick nick" or "+pzh-o nick nick"
message.modes = [];
var args = message.params.slice(2);
var modes = message.modestring.split("");
var prefixes = {"+":true,"-":false};
var argModes = { // TODO: Use the "CHANMODES" sent by the server on connect to make this dict
"a": "nick",
"b": "mask",
"e": "mask",
"f": "lines:second",
"h": "nick",
"I": "mask",
"J": "joins:second",
"k": "key",
"l": "max_users",
"L": "channel",
"o": "nick",
"O": "nick",
"q": "nick",
"v": "nick"
};
for (var i = 0, set_mode = true; i < modes.length; i++) {
var mode = modes[i];
if (mode in prefixes) {
set_mode = prefixes[mode];
} else if (mode in argModes) {
message.modes.push({
mode: mode,
parameter: args.shift(),
set: set_mode
});
} else {
message.modes.push({
mode: mode,
set: set_mode
});
}
}
},
"001": function (message) {
message.replyname = "RPL_WELCOME";
},
"307": function (message) {
// :<server> 307 <me> <nick> :is a registered nick
// FIXME(Havvy): Only accounts for UnrealIRCd
message.replyname = "RPL_WHOISREGNICK";
message.nickname = message.params[1];
},
"310": function (message) {
// :<server> 310 <me> <nick> :is available for help.
// FIXME(Havvy): Bahumat & AustHex use this numeric for other purposes.
// UnrealIRCd Only
message.replyname = "RPL_WHOISHELPOP";
},
"311": function (message) {
// :<server> 311 <me> <nick> <username> <hostmask> * <realname>
message.replyname = "RPL_WHOISUSER";
message.nickname = message.params[1];
message.username = message.params[2];
message.hostname = message.params[3];
message.realname = message.params[5];
message.hostmask = {
nickname: message.params[1],
username: message.params[2],
hostname: message.params[3]
};
},
"312": function (message) {
// :<server> 312 <me> <nick> <server> :<info>
message.replyname = "RPL_WHOISSERVER";
message.nickname = message.params[1];
message.server = message.params[2];
message.serverInfo = message.params[3];
},
"313": function (message) {
// :<server> 313 <me> <nickname> :is a Network Administrator
message.replyname = "RPL_WHOISOPERATOR";
message.nickname = message.params[1];
message.permissions = message.params[2];
},
"317": function (message) {
// :<server> 317 <me> <nick> <seconds> <unixtime> :seconds idle, signon time
message.replyname = "RPL_WHOISIDLE";
message.nickname = message.params[1];
message.seconds = message.params[2];
message.since = message.params[3];
},
"318": function (message) {
// :<server> 318 <me> <nick> :End of /WHOIS list.
message.replyname = "RPL_ENDOFWHOIS";
message.nickname = message.params[1];
},
"319": function (message) {
// :<server> 319 <me> <nickname> :<channels>
// channel format: <power sigil>?<channel sigil><name>
message.replyname = "RPL_WHOISCHANNELS";
message.nickname = message.params[1];
message.channels = message.params[2].toLowerCase();
},
"330": function (message) {
// :<server> 330 <me> <nickname> <ident> :is logged in as
// Nonstandard, but used on most IRCds.
message.replyname = "RPL_WHOISLOGGEDIN";
message.nickname = message.params[1];
message.identifiedas = message.params[2];
},
"332": function (message) {
// :<server> 332 <me> <channel> :<topic>
message.replyname = "RPL_TOPIC";
message.channel = message.params[1].toLowerCase();
message.topic = message.params[2];
},
"333": function (message) {
// :<server> 333 <me> <channel> <who> <timestamp>
message.replyname = "RPL_TOPICWHOTIME";
message.channel = message.params[1].toLowerCase();
message.who = message.params[2];
message.timestamp = Number(message.params[3]);
},
"335": function (message) {
// :<server> 335 <me> <nickname> :is a Bot on <network name>
// UnrealIRCd Only
message.replyname = "RPL_WHOISBOT";
},
"346": function (message) {
// :<server> 346 <me> <channel> <hostmaskPattern> <setter> <timestamp>
message.replyname = "RPL_INVITELIST";
message.channel = message.params[1];
message.hostmaskPattern = message.params[2];
message.setter = message.params[3];
message.timestamp = message.params[4];
},
"348": function (message) {
// :<server> 348 <me> <channel> <hostmaskPattern> <setter> <timestamp>
message.replyname = "RPL_EXCEPTLIST";
message.channel = message.params[1];
message.hostmaskPattern = message.params[2];
message.setter = message.params[3];
message.timestamp = message.params[4];
},
"353": function (message) {
// :<server> 353 <me> <sigil> <channel> :<nicknames>
// <nickname> := ModeChar <> NickName
// The replyname really doesn"t have an "E" in it.
// The sigil is either an `=` or `@`.
// Not sure there's any meaning behind it.
message.replyname = "RPL_NAMREPLY";
message.channel = message.params[2].toLowerCase();
message.nicknames = message.params[3].trim().split(" ");
},
"366": function (message) {
// :<server> 366 <me> <channel> :End of /NAMES list.
message.replyname = "RPL_ENDOFNAMES";
message.channel = message.params[1].toLowerCase();
},
"367": function (message) {
// :<server> 367 <me> <channel> <hostmaskPattern> <setter> <timestamp>
message.replyname = "RPL_BANLIST";
message.channel = message.params[1];
message.hostmaskPattern = message.params[2];
message.setter = message.params[3];
message.timestamp = message.params[4];
},
"372": function (message) {
//:<server> 372 <me> :<motdLine>
message.replyname = "RPL_MOTD";
},
"375": function (message) {
//:<server> 375 <me> :<arbitrary>
message.replyname = "RPL_STARTOFMOTD";
},
"376": function (message) {
//:<server> 376 <me> :End of /MOTD command.
message.replyname = "RPL_ENDOFMOTD";
},
"378": function (message) {
// :<server> 378 <me> <nickname> :is connecting from <hostmask> <ip>
message.replyname = "RPL_WHOISHOST";
message.nickname = message.params[1];
var words = message.params[2].split(" ");
message.hostmask = words[3];
message.ip = words[4];
},
"401": function (message) {
// :<server> 401 <me> <nick> :No such nick/channel
message.replyname= "ERR_NOSUCHNICK";
message.nickname = message.params[1];
},
"403": function (message) {
// :<server> 403 <me> <channel> :<reason>
message.replyname = "ERR_NOSUCHCHANNEL";
message.channel = message.params[1].toLowerCase();
},
"405": function (message) {
// :<server> 405 <me> <channel> :<reason>
message.replyname = "ERR_TOOMANYCHANNELS";
message.channel = message.params[1].toLowerCase();
},
"421": function (message) {
// :<server> 421 <me> <command> :Unknown command
message.replyname = "ERR_UNKNOWNCOMMAND";
message.unknownCommand = message.params[1].toLowerCase();
},
"437": function (message) {
// :<server> 437 <me> <target> :Nick/channel is temporarily unavailable
message.replyname = "ERR_UNAVAILRESOURCE";
// TODO: Determine whether resource is channel or nickname.
message.channel = message.params[1].toLowerCase();
message.nickname = message.params[1];
message.resource = message.params[1];
},
"442": function (message) {
// :<server> 442 <me> <channel> :You're not on that channel
message.replyname = "ERR_NOTONCHANNEL";
message.channel = message.params[1].toLowerCase();
message.reason = message.params[2];
},
"461": function (message) {
// :<server> 461 <me> <command> :Not enough parameters
message.replyname = "ERR_NEEDMOREPARAMS";
message.command = message.params[1];
},
"471": function (message) {
// :server> 471 <me> <channel> :Cannot join channel (+l)
message.replyname = "ERR_CHANNELISFULL";
message.channel = message.params[1].toLowerCase();
},
"473": function (message) {
// :server 473 <me> <channel> :Cannot join channel (+i)
message.replyname = "ERR_INVITEONLYCHAN";
message.channel = message.params[1].toLowerCase();
},
"474": function (message) {
// :<server> 474 <me> <channel> :Cannot join channel (+b)
message.replyname = "ERR_BANNEDFROMCHAN";
message.channel = message.params[1].toLowerCase();
},
"475": function (message) {
// :server> 475 <me> <channel> :Cannot join channel (+k)
message.replyname = "ERR_BADCHANNELKEY";
message.channel = message.params[1].toLowerCase();
},
"477": function (message) {
// :server> 477 <me> <channel> :You need a registered nick to join that channel.
message.replyname = "ERR_NEEDREGGEDNICK";
message.channel = message.params[1].toLowerCase();
},
"489": function (message) {
// :<server> 489 <me> <channel> :Cannot join channel (SSL is required)
message.replyname = "ERR_SECUREONLYCHAN";
message.channel = message.params[1].toLowerCase();
},
"520": function (message) {
// :server 520 <me> :Cannot join channel <channel> (IRCops only)
// :server 520 <me> <channel> :<reason>
message.replyname = "ERR_OPERONLY";
if (message.params.length === 2) {
// UnrealIRCd being stupid.
message.channel = message.params[1].split(" ")[3].toLowerCase();
} else {
message.channel = message.params[1].toLowerCase();
}
},
"671": function (message) {
// :<server> 671 <me> <nickname> :is using a secure connection
message.replyname = "RPL_WHOISSECURE"
}
};
var Message = function (raw) {
var message = Object.create(new RFCMessage(raw));
message.command = message.command.toLowerCase();
// message.hostmask is either null or an object with
// nickname, username, hostname properties.
message.hostmask = message.parseHostmaskFromPrefix();
message.nickname = message.hostmask && message.hostmask.nickname;
if (extensions[message.command]) {
extensions[message.command](message);
}
return message;
};
Message.extensions = extensions;
module.exports = Message;