summaryrefslogtreecommitdiffstats
path: root/comm/chat/protocols/irc/ircBase.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'comm/chat/protocols/irc/ircBase.sys.mjs')
-rw-r--r--comm/chat/protocols/irc/ircBase.sys.mjs1768
1 files changed, 1768 insertions, 0 deletions
diff --git a/comm/chat/protocols/irc/ircBase.sys.mjs b/comm/chat/protocols/irc/ircBase.sys.mjs
new file mode 100644
index 0000000000..9127dd4e24
--- /dev/null
+++ b/comm/chat/protocols/irc/ircBase.sys.mjs
@@ -0,0 +1,1768 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This contains the implementation for the basic Internet Relay Chat (IRC)
+ * protocol covered by RFCs 2810, 2811, 2812 and 2813 (which obsoletes RFC
+ * 1459). RFC 2812 covers the client commands and protocol.
+ * RFC 2810: Internet Relay Chat: Architecture
+ * http://tools.ietf.org/html/rfc2810
+ * RFC 2811: Internet Relay Chat: Channel Management
+ * http://tools.ietf.org/html/rfc2811
+ * RFC 2812: Internet Relay Chat: Client Protocol
+ * http://tools.ietf.org/html/rfc2812
+ * RFC 2813: Internet Relay Chat: Server Protocol
+ * http://tools.ietf.org/html/rfc2813
+ * RFC 1459: Internet Relay Chat Protocol
+ * http://tools.ietf.org/html/rfc1459
+ */
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+import {
+ l10nHelper,
+ nsSimpleEnumerator,
+} from "resource:///modules/imXPCOMUtils.sys.mjs";
+import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";
+import { ircHandlerPriorities } from "resource:///modules/ircHandlerPriorities.sys.mjs";
+import {
+ ctcpFormatToText,
+ conversationErrorMessage,
+ displayMessage,
+ kListRefreshInterval,
+} from "resource:///modules/ircUtils.sys.mjs";
+
+const lazy = {};
+XPCOMUtils.defineLazyGetter(lazy, "_", () =>
+ l10nHelper("chrome://chat/locale/irc.properties")
+);
+
+// Display the message and remove them from the rooms they're in.
+function leftRoom(aAccount, aNicks, aChannels, aSource, aReason, aKicked) {
+ let msgId = "message." + (aKicked ? "kicked" : "parted");
+ // If a part message was included, include it.
+ let reason = aReason ? lazy._(msgId + ".reason", aReason) : "";
+ function __(aNick, aYou) {
+ // If the user is kicked, we need to say who kicked them.
+ let msgId2 = msgId + (aYou ? ".you" : "");
+ if (aKicked) {
+ if (aYou) {
+ return lazy._(msgId2, aSource, reason);
+ }
+ return lazy._(msgId2, aNick, aSource, reason);
+ }
+ if (aYou) {
+ return lazy._(msgId2, reason);
+ }
+ return lazy._(msgId2, aNick, reason);
+ }
+
+ for (let channelName of aChannels) {
+ if (!aAccount.conversations.has(channelName)) {
+ // Handle when we closed the window.
+ continue;
+ }
+ let conversation = aAccount.getConversation(channelName);
+ for (let nick of aNicks) {
+ let msg;
+ if (aAccount.normalize(nick) == aAccount.normalize(aAccount._nickname)) {
+ msg = __(nick, true);
+ // If the user left, mark the conversation as no longer being active.
+ conversation.left = true;
+ } else {
+ msg = __(nick);
+ }
+
+ conversation.writeMessage(aSource, msg, { system: true });
+ conversation.removeParticipant(nick);
+ }
+ }
+ return true;
+}
+
+function writeMessage(aAccount, aMessage, aString, aType) {
+ let type = {};
+ type[aType] = true;
+ type.tags = aMessage.tags;
+ aAccount
+ .getConversation(aMessage.origin)
+ .writeMessage(aMessage.origin, aString, type);
+ return true;
+}
+
+// If aNoLastParam is true, the last parameter is not printed out.
+function serverMessage(aAccount, aMsg, aNoLastParam) {
+ // If we don't want to show messages from the server, just mark it as handled.
+ if (!aAccount._showServerTab) {
+ return true;
+ }
+
+ return writeMessage(
+ aAccount,
+ aMsg,
+ aMsg.params.slice(1, aNoLastParam ? -1 : undefined).join(" "),
+ "system"
+ );
+}
+
+function serverErrorMessage(aAccount, aMessage, aError) {
+ // If we don't want to show messages from the server, just mark it as handled.
+ if (!aAccount._showServerTab) {
+ return true;
+ }
+
+ return writeMessage(aAccount, aMessage, aError, "error");
+}
+
+function addMotd(aAccount, aMessage) {
+ // If there is no current MOTD to append to, start a new one.
+ if (!aAccount._motd) {
+ aAccount._motd = [];
+ }
+
+ // Traditionally, MOTD messages start with "- ", but this is not always
+ // true, try to handle that sanely.
+ let message = aMessage.params[1];
+ if (message.startsWith("-")) {
+ message = message.slice(1).trim();
+ }
+ // And traditionally, the initial message ends in " -", remove that.
+ if (message.endsWith("-")) {
+ message = message.slice(0, -1).trim();
+ }
+
+ // Actually add the message (if it still exists).
+ if (message) {
+ aAccount._motd.push(message);
+ }
+
+ // Oh, also some servers don't send a RPL_ENDOFMOTD (e.g. irc.ppy.sh), so if
+ // we don't receive another MOTD message after 1 second, consider it to be
+ // RPL_ENDOFMOTD.
+ clearTimeout(aAccount._motdTimer);
+ aAccount._motdTimer = setTimeout(
+ ircBase.commands["376"].bind(aAccount),
+ 1000,
+ aMessage
+ );
+
+ return true;
+}
+
+// See RFCs 2811 & 2812 (which obsoletes RFC 1459) for a description of these
+// commands.
+export var ircBase = {
+ // Parameters
+ name: "RFC 2812", // Name identifier
+ priority: ircHandlerPriorities.DEFAULT_PRIORITY,
+ isEnabled: () => true,
+
+ // The IRC commands that can be handled.
+ commands: {
+ ERROR(aMessage) {
+ // ERROR <error message>
+ // Client connection has been terminated.
+ if (!this.disconnecting) {
+ // We received an ERROR message when we weren't expecting it, this is
+ // probably the server giving us a ping timeout.
+ this.WARN("Received unexpected ERROR response:\n" + aMessage.params[0]);
+ this.gotDisconnected(
+ Ci.prplIAccount.ERROR_NETWORK_ERROR,
+ lazy._("connection.error.lost")
+ );
+ } else {
+ // We received an ERROR message when expecting it (i.e. we've sent a
+ // QUIT command). Notify account manager.
+ this.gotDisconnected();
+ }
+ return true;
+ },
+ INVITE(aMessage) {
+ // INVITE <nickname> <channel>
+ let channel = aMessage.params[1];
+ this.addChatRequest(
+ channel,
+ () => {
+ this.joinChat(this.getChatRoomDefaultFieldValues(channel));
+ },
+ request => {
+ // Inform the user when an invitation was automatically ignored.
+ if (!request) {
+ // Otherwise just notify the user.
+ this.getConversation(channel).writeMessage(
+ aMessage.origin,
+ lazy._("message.inviteReceived", aMessage.origin, channel),
+ { system: true }
+ );
+ }
+ }
+ );
+ return true;
+ },
+ JOIN(aMessage) {
+ // JOIN ( <channel> *( "," <channel> ) [ <key> *( "," <key> ) ] ) / "0"
+ // Iterate over each channel.
+ for (let channelName of aMessage.params[0].split(",")) {
+ let conversation = this.getConversation(channelName);
+
+ // Check whether we joined the channel or if someone else did.
+ if (
+ this.normalize(aMessage.origin, this.userPrefixes) ==
+ this.normalize(this._nickname)
+ ) {
+ // If we join, clear the participants list to avoid errors with
+ // repeated participants.
+ conversation.removeAllParticipants();
+ conversation.left = false;
+ conversation.joining = false;
+
+ // Update the channel name if it has improper capitalization.
+ if (channelName != conversation.name) {
+ conversation._name = channelName;
+ conversation.notifyObservers(null, "update-conv-title");
+ }
+
+ // If the user parted from this room earlier, confirm the rejoin.
+ if (conversation._rejoined) {
+ conversation.writeMessage(
+ aMessage.origin,
+ lazy._("message.rejoined"),
+ {
+ system: true,
+ }
+ );
+ delete conversation._rejoined;
+ }
+
+ // Ensure chatRoomFields information is available for reconnection.
+ if (!conversation.chatRoomFields) {
+ this.WARN(
+ "Opening a MUC without storing its " +
+ "prplIChatRoomFieldValues first."
+ );
+ conversation.chatRoomFields =
+ this.getChatRoomDefaultFieldValues(channelName);
+ }
+ } else {
+ // Don't worry about adding ourself, RPL_NAMREPLY takes care of that
+ // case.
+ conversation.getParticipant(aMessage.origin, true);
+ let msg = lazy._("message.join", aMessage.origin, aMessage.source);
+ conversation.writeMessage(aMessage.origin, msg, {
+ system: true,
+ noLinkification: true,
+ });
+ }
+ }
+ // If the joiner is a buddy, mark as online.
+ let buddy = this.buddies.get(aMessage.origin);
+ if (buddy) {
+ buddy.setStatus(Ci.imIStatusInfo.STATUS_AVAILABLE, "");
+ }
+ return true;
+ },
+ KICK(aMessage) {
+ // KICK <channel> *( "," <channel> ) <user> *( "," <user> ) [<comment>]
+ let comment = aMessage.params.length == 3 ? aMessage.params[2] : null;
+ // Some servers (moznet) send the kicker as the comment.
+ if (comment == aMessage.origin) {
+ comment = null;
+ }
+ return leftRoom(
+ this,
+ aMessage.params[1].split(","),
+ aMessage.params[0].split(","),
+ aMessage.origin,
+ comment,
+ true
+ );
+ },
+ MODE(aMessage) {
+ // MODE <nickname> *( ( "+" / "-") *( "i" / "w" / "o" / "O" / "r" ) )
+ // MODE <channel> *( ( "-" / "+" ) *<modes> *<modeparams> )
+ if (this.isMUCName(aMessage.params[0])) {
+ // If the first parameter is a channel name, a channel/participant mode
+ // was updated.
+ this.getConversation(aMessage.params[0]).setMode(
+ aMessage.params[1],
+ aMessage.params.slice(2),
+ aMessage.origin
+ );
+
+ return true;
+ }
+
+ // Otherwise the user's own mode is being returned to them.
+ return this.setUserMode(
+ aMessage.params[0],
+ aMessage.params[1],
+ aMessage.origin,
+ !this._userModeReceived
+ );
+ },
+ NICK(aMessage) {
+ // NICK <nickname>
+ this.changeBuddyNick(aMessage.origin, aMessage.params[0]);
+ return true;
+ },
+ NOTICE(aMessage) {
+ // NOTICE <msgtarget> <text>
+ // If the message is from the server, don't show it unless the user wants
+ // to see it.
+ if (!this.connected || aMessage.origin == this._currentServerName) {
+ return serverMessage(this, aMessage);
+ }
+ return displayMessage(this, aMessage, { notification: true });
+ },
+ PART(aMessage) {
+ // PART <channel> *( "," <channel> ) [ <Part Message> ]
+ return leftRoom(
+ this,
+ [aMessage.origin],
+ aMessage.params[0].split(","),
+ aMessage.source,
+ aMessage.params.length == 2 ? aMessage.params[1] : null
+ );
+ },
+ PING(aMessage) {
+ // PING <server1> [ <server2> ]
+ // Keep the connection alive.
+ this.sendMessage("PONG", aMessage.params[0]);
+ return true;
+ },
+ PONG(aMessage) {
+ // PONG <server> [ <server2> ]
+ let pongTime = aMessage.params[1];
+
+ // Ping to keep the connection alive.
+ if (pongTime.startsWith("_")) {
+ this._socket.cancelDisconnectTimer();
+ return true;
+ }
+ // Otherwise, the ping was from a user command.
+ return this.handlePingReply(aMessage.origin, pongTime);
+ },
+ PRIVMSG(aMessage) {
+ // PRIVMSG <msgtarget> <text to be sent>
+ // Display message in conversation
+ return displayMessage(this, aMessage);
+ },
+ QUIT(aMessage) {
+ // QUIT [ < Quit Message> ]
+ // Some IRC servers automatically prefix a "Quit: " string. Remove the
+ // duplication and use a localized version.
+ let quitMsg = aMessage.params[0] || "";
+ if (quitMsg.startsWith("Quit: ")) {
+ quitMsg = quitMsg.slice(6); // "Quit: ".length
+ }
+ // If a quit message was included, show it.
+ let nick = aMessage.origin;
+ let msg = lazy._(
+ "message.quit",
+ nick,
+ quitMsg.length ? lazy._("message.quit2", quitMsg) : ""
+ );
+ // Loop over every conversation with the user and display that they quit.
+ this.conversations.forEach(conversation => {
+ if (conversation.isChat && conversation._participants.has(nick)) {
+ conversation.writeMessage(nick, msg, { system: true });
+ conversation.removeParticipant(nick);
+ }
+ });
+
+ // Remove from the whois table.
+ this.removeBuddyInfo(nick);
+
+ // If the leaver is a buddy, mark as offline.
+ let buddy = this.buddies.get(nick);
+ if (buddy) {
+ buddy.setStatus(Ci.imIStatusInfo.STATUS_OFFLINE, "");
+ }
+
+ // If we wanted this nickname, grab it.
+ if (nick == this._requestedNickname && nick != this._nickname) {
+ this.changeNick(this._requestedNickname);
+ clearTimeout(this._nickInUseTimeout);
+ delete this._nickInUseTimeout;
+ }
+ return true;
+ },
+ SQUIT(aMessage) {
+ // <server> <comment>
+ return true;
+ },
+ TOPIC(aMessage) {
+ // TOPIC <channel> [ <topic> ]
+ // Show topic as a message.
+ let conversation = this.getConversation(aMessage.params[0]);
+ let topic = aMessage.params[1];
+ // Set the topic in the conversation and update the UI.
+ conversation.setTopic(
+ topic ? ctcpFormatToText(topic) : "",
+ aMessage.origin
+ );
+ return true;
+ },
+ "001": function (aMessage) {
+ // RPL_WELCOME
+ // Welcome to the Internet Relay Network <nick>!<user>@<host>
+ this._socket.resetPingTimer();
+ // This seems a little strange, but we don't differentiate between a
+ // nickname and the servername since it can be ambiguous.
+ this._currentServerName = aMessage.origin;
+
+ // Clear user mode.
+ this._modes = new Set();
+ this._userModeReceived = false;
+
+ // Check if autoUserMode is set in the account preferences. If it is set,
+ // then notify the server that the user wants a specific mode.
+ if (this.prefs.prefHasUserValue("autoUserMode")) {
+ this.sendMessage("MODE", [
+ this._nickname,
+ this.getString("autoUserMode"),
+ ]);
+ }
+
+ // Check if our nick has changed.
+ if (aMessage.params[0] != this._nickname) {
+ this.changeBuddyNick(this._nickname, aMessage.params[0]);
+ }
+
+ // Request our own whois entry so we can set the prefix.
+ this.requestCurrentWhois(this._nickname);
+
+ // If our status is Unavailable, tell the server.
+ if (
+ this.imAccount.statusInfo.statusType < Ci.imIStatusInfo.STATUS_AVAILABLE
+ ) {
+ this.observe(null, "status-changed");
+ }
+
+ // Check if any of our buddies are online!
+ const kInitialIsOnDelay = 1000;
+ this._isOnTimer = setTimeout(this.sendIsOn.bind(this), kInitialIsOnDelay);
+
+ // If we didn't handle all the CAPs we added, something is wrong.
+ if (this._requestedCAPs.size) {
+ this.ERROR(
+ "Connected without removing CAPs: " + [...this._requestedCAPs]
+ );
+ }
+
+ // Done!
+ this.reportConnected();
+ return serverMessage(this, aMessage);
+ },
+ "002": function (aMessage) {
+ // RPL_YOURHOST
+ // Your host is <servername>, running version <ver>
+ return serverMessage(this, aMessage);
+ },
+ "003": function (aMessage) {
+ // RPL_CREATED
+ // This server was created <date>
+ // TODO parse this date and keep it for some reason? Do we care?
+ return serverMessage(this, aMessage);
+ },
+ "004": function (aMessage) {
+ // RPL_MYINFO
+ // <servername> <version> <available user modes> <available channel modes>
+ // TODO parse the available modes, let the UI respond and inform the user
+ return serverMessage(this, aMessage);
+ },
+ "005": function (aMessage) {
+ // RPL_BOUNCE
+ // Try server <server name>, port <port number>
+ return serverMessage(this, aMessage);
+ },
+
+ /*
+ * Handle response to TRACE message
+ */
+ 200(aMessage) {
+ // RPL_TRACELINK
+ // Link <version & debug level> <destination> <next server>
+ // V<protocol version> <link updateime in seconds> <backstream sendq>
+ // <upstream sendq>
+ return serverMessage(this, aMessage);
+ },
+ 201(aMessage) {
+ // RPL_TRACECONNECTING
+ // Try. <class> <server>
+ return serverMessage(this, aMessage);
+ },
+ 202(aMessage) {
+ // RPL_TRACEHANDSHAKE
+ // H.S. <class> <server>
+ return serverMessage(this, aMessage);
+ },
+ 203(aMessage) {
+ // RPL_TRACEUNKNOWN
+ // ???? <class> [<client IP address in dot form>]
+ return serverMessage(this, aMessage);
+ },
+ 204(aMessage) {
+ // RPL_TRACEOPERATOR
+ // Oper <class> <nick>
+ return serverMessage(this, aMessage);
+ },
+ 205(aMessage) {
+ // RPL_TRACEUSER
+ // User <class> <nick>
+ return serverMessage(this, aMessage);
+ },
+ 206(aMessage) {
+ // RPL_TRACESERVER
+ // Serv <class> <int>S <int>C <server> <nick!user|*!*>@<host|server>
+ // V<protocol version>
+ return serverMessage(this, aMessage);
+ },
+ 207(aMessage) {
+ // RPL_TRACESERVICE
+ // Service <class> <name> <type> <active type>
+ return serverMessage(this, aMessage);
+ },
+ 208(aMessage) {
+ // RPL_TRACENEWTYPE
+ // <newtype> 0 <client name>
+ return serverMessage(this, aMessage);
+ },
+ 209(aMessage) {
+ // RPL_TRACECLASS
+ // Class <class> <count>
+ return serverMessage(this, aMessage);
+ },
+ 210(aMessage) {
+ // RPL_TRACERECONNECTION
+ // Unused.
+ return serverMessage(this, aMessage);
+ },
+
+ /*
+ * Handle stats messages.
+ **/
+ 211(aMessage) {
+ // RPL_STATSLINKINFO
+ // <linkname> <sendq> <sent messages> <sent Kbytes> <received messages>
+ // <received Kbytes> <time open>
+ return serverMessage(this, aMessage);
+ },
+ 212(aMessage) {
+ // RPL_STATSCOMMAND
+ // <command> <count> <byte count> <remote count>
+ return serverMessage(this, aMessage);
+ },
+ 213(aMessage) {
+ // RPL_STATSCLINE
+ // Non-generic
+ return serverMessage(this, aMessage);
+ },
+ 214(aMessage) {
+ // RPL_STATSNLINE
+ // Non-generic
+ return serverMessage(this, aMessage);
+ },
+ 215(aMessage) {
+ // RPL_STATSILINE
+ // Non-generic
+ return serverMessage(this, aMessage);
+ },
+ 216(aMessage) {
+ // RPL_STATSKLINE
+ // Non-generic
+ return serverMessage(this, aMessage);
+ },
+ 217(aMessage) {
+ // RPL_STATSQLINE
+ // Non-generic
+ return serverMessage(this, aMessage);
+ },
+ 218(aMessage) {
+ // RPL_STATSYLINE
+ // Non-generic
+ return serverMessage(this, aMessage);
+ },
+ 219(aMessage) {
+ // RPL_ENDOFSTATS
+ // <stats letter> :End of STATS report
+ return serverMessage(this, aMessage);
+ },
+
+ 221(aMessage) {
+ // RPL_UMODEIS
+ // <user mode string>
+ return this.setUserMode(
+ aMessage.params[0],
+ aMessage.params[1],
+ aMessage.origin,
+ true
+ );
+ },
+
+ /*
+ * Services
+ */
+ 231(aMessage) {
+ // RPL_SERVICEINFO
+ // Non-generic
+ return serverMessage(this, aMessage);
+ },
+ 232(aMessage) {
+ // RPL_ENDOFSERVICES
+ // Non-generic
+ return serverMessage(this, aMessage);
+ },
+ 233(aMessage) {
+ // RPL_SERVICE
+ // Non-generic
+ return serverMessage(this, aMessage);
+ },
+
+ /*
+ * Server
+ */
+ 234(aMessage) {
+ // RPL_SERVLIST
+ // <name> <server> <mask> <type> <hopcount> <info>
+ return serverMessage(this, aMessage);
+ },
+ 235(aMessage) {
+ // RPL_SERVLISTEND
+ // <mask> <type> :End of service listing
+ return serverMessage(this, aMessage, true);
+ },
+
+ /*
+ * Stats
+ * TODO some of these have real information we could try to parse.
+ */
+ 240(aMessage) {
+ // RPL_STATSVLINE
+ // Non-generic
+ return serverMessage(this, aMessage);
+ },
+ 241(aMessage) {
+ // RPL_STATSLLINE
+ // Non-generic
+ return serverMessage(this, aMessage);
+ },
+ 242(aMessage) {
+ // RPL_STATSUPTIME
+ // :Server Up %d days %d:%02d:%02d
+ return serverMessage(this, aMessage);
+ },
+ 243(aMessage) {
+ // RPL_STATSOLINE
+ // O <hostmask> * <name>
+ return serverMessage(this, aMessage);
+ },
+ 244(aMessage) {
+ // RPL_STATSHLINE
+ // Non-generic
+ return serverMessage(this, aMessage);
+ },
+ 245(aMessage) {
+ // RPL_STATSSLINE
+ // Non-generic
+ // Note that this is given as 244 in RFC 2812, this seems to be incorrect.
+ return serverMessage(this, aMessage);
+ },
+ 246(aMessage) {
+ // RPL_STATSPING
+ // Non-generic
+ return serverMessage(this, aMessage);
+ },
+ 247(aMessage) {
+ // RPL_STATSBLINE
+ // Non-generic
+ return serverMessage(this, aMessage);
+ },
+ 250(aMessage) {
+ // RPL_STATSDLINE
+ // Non-generic
+ return serverMessage(this, aMessage);
+ },
+
+ /*
+ * LUSER messages
+ */
+ 251(aMessage) {
+ // RPL_LUSERCLIENT
+ // :There are <integer> users and <integer> services on <integer> servers
+ return serverMessage(this, aMessage);
+ },
+ 252(aMessage) {
+ // RPL_LUSEROP, 0 if not sent
+ // <integer> :operator(s) online
+ return serverMessage(this, aMessage);
+ },
+ 253(aMessage) {
+ // RPL_LUSERUNKNOWN, 0 if not sent
+ // <integer> :unknown connection(s)
+ return serverMessage(this, aMessage);
+ },
+ 254(aMessage) {
+ // RPL_LUSERCHANNELS, 0 if not sent
+ // <integer> :channels formed
+ return serverMessage(this, aMessage);
+ },
+ 255(aMessage) {
+ // RPL_LUSERME
+ // :I have <integer> clients and <integer> servers
+ return serverMessage(this, aMessage);
+ },
+
+ /*
+ * ADMIN messages
+ */
+ 256(aMessage) {
+ // RPL_ADMINME
+ // <server> :Administrative info
+ return serverMessage(this, aMessage);
+ },
+ 257(aMessage) {
+ // RPL_ADMINLOC1
+ // :<admin info>
+ // City, state & country
+ return serverMessage(this, aMessage);
+ },
+ 258(aMessage) {
+ // RPL_ADMINLOC2
+ // :<admin info>
+ // Institution details
+ return serverMessage(this, aMessage);
+ },
+ 259(aMessage) {
+ // RPL_ADMINEMAIL
+ // :<admin info>
+ // TODO We could parse this for a contact email.
+ return serverMessage(this, aMessage);
+ },
+
+ /*
+ * TRACELOG
+ */
+ 261(aMessage) {
+ // RPL_TRACELOG
+ // File <logfile> <debug level>
+ return serverMessage(this, aMessage);
+ },
+ 262(aMessage) {
+ // RPL_TRACEEND
+ // <server name> <version & debug level> :End of TRACE
+ return serverMessage(this, aMessage, true);
+ },
+
+ /*
+ * Try again.
+ */
+ 263(aMessage) {
+ // RPL_TRYAGAIN
+ // <command> :Please wait a while and try again.
+ if (aMessage.params[1] == "LIST" && this._pendingList) {
+ // We may receive this from servers which rate-limit LIST if the
+ // server believes us to be asking for LIST data too soon after the
+ // previous request.
+ // Tidy up as we won't be receiving any more channels.
+ this._sendRemainingRoomInfo();
+ // Fake the last LIST time so that we may try again in one hour.
+ const kHour = 60 * 60 * 1000;
+ this._lastListTime = Date.now() - kListRefreshInterval + kHour;
+ return true;
+ }
+ return serverMessage(this, aMessage);
+ },
+
+ 265(aMessage) {
+ // nonstandard
+ // :Current Local Users: <integer> Max: <integer>
+ return serverMessage(this, aMessage);
+ },
+ 266(aMessage) {
+ // nonstandard
+ // :Current Global Users: <integer> Max: <integer>
+ return serverMessage(this, aMessage);
+ },
+ 300(aMessage) {
+ // RPL_NONE
+ // Non-generic
+ return serverMessage(this, aMessage);
+ },
+
+ /*
+ * Status messages
+ */
+ 301(aMessage) {
+ // RPL_AWAY
+ // <nick> :<away message>
+ // TODO set user as away on buddy list / conversation lists
+ // TODO Display an autoResponse if this is after sending a private message
+ // If the conversation is waiting for a response, it's received one.
+ if (this.conversations.has(aMessage.params[1])) {
+ delete this.getConversation(aMessage.params[1])._pendingMessage;
+ }
+ return this.setWhois(aMessage.params[1], { away: aMessage.params[2] });
+ },
+ 302(aMessage) {
+ // RPL_USERHOST
+ // :*1<reply> *( " " <reply )"
+ // reply = nickname [ "*" ] "=" ( "+" / "-" ) hostname
+ // TODO Can tell op / away from this
+ return false;
+ },
+ 303(aMessage) {
+ // RPL_ISON
+ // :*1<nick> *( " " <nick> )"
+ // Set the status of the buddies based the latest ISON response.
+ let receivedBuddyNames = [];
+ // The buddy names as returned by the server.
+ if (aMessage.params.length > 1) {
+ receivedBuddyNames = aMessage.params[1].trim().split(" ");
+ }
+
+ // This was received in response to the last ISON message sent.
+ for (let buddyName of this.pendingIsOnQueue) {
+ // If the buddy name is in the list returned from the server, they're
+ // online.
+ let status = !receivedBuddyNames.includes(buddyName)
+ ? Ci.imIStatusInfo.STATUS_OFFLINE
+ : Ci.imIStatusInfo.STATUS_AVAILABLE;
+
+ // Set the status with no status message, only if the buddy actually
+ // exists in the buddy list.
+ let buddy = this.buddies.get(buddyName);
+ if (buddy) {
+ buddy.setStatus(status, "");
+ }
+ }
+ return true;
+ },
+ 305(aMessage) {
+ // RPL_UNAWAY
+ // :You are no longer marked as being away
+ this.isAway = false;
+ return true;
+ },
+ 306(aMessage) {
+ // RPL_NOWAWAY
+ // :You have been marked as away
+ this.isAway = true;
+ return true;
+ },
+
+ /*
+ * WHOIS
+ */
+ 311(aMessage) {
+ // RPL_WHOISUSER
+ // <nick> <user> <host> * :<real name>
+ // <username>@<hostname>
+ let nick = aMessage.params[1];
+ let source = aMessage.params[2] + "@" + aMessage.params[3];
+ // Some servers obfuscate the host when sending messages. Therefore,
+ // we set the account prefix by using the host from this response.
+ // We store it separately to avoid glitches due to the whois entry
+ // being temporarily deleted during future updates of the entry.
+ if (this.normalize(nick) == this.normalize(this._nickname)) {
+ this.prefix = "!" + source;
+ }
+ return this.setWhois(nick, {
+ realname: aMessage.params[5],
+ connectedFrom: source,
+ });
+ },
+ 312(aMessage) {
+ // RPL_WHOISSERVER
+ // <nick> <server> :<server info>
+ return this.setWhois(aMessage.params[1], {
+ serverName: aMessage.params[2],
+ serverInfo: aMessage.params[3],
+ });
+ },
+ 313(aMessage) {
+ // RPL_WHOISOPERATOR
+ // <nick> :is an IRC operator
+ return this.setWhois(aMessage.params[1], { ircOp: true });
+ },
+ 314(aMessage) {
+ // RPL_WHOWASUSER
+ // <nick> <user> <host> * :<real name>
+ let source = aMessage.params[2] + "@" + aMessage.params[3];
+ return this.setWhois(aMessage.params[1], {
+ offline: true,
+ realname: aMessage.params[5],
+ connectedFrom: source,
+ });
+ },
+ 315(aMessage) {
+ // RPL_ENDOFWHO
+ // <name> :End of WHO list
+ return false;
+ },
+ 316(aMessage) {
+ // RPL_WHOISCHANOP
+ // Non-generic
+ return false;
+ },
+ 317(aMessage) {
+ // RPL_WHOISIDLE
+ // <nick> <integer> :seconds idle
+ return this.setWhois(aMessage.params[1], {
+ lastActivity: parseInt(aMessage.params[2]),
+ });
+ },
+ 318(aMessage) {
+ // RPL_ENDOFWHOIS
+ // <nick> :End of WHOIS list
+ // We've received everything about WHOIS, tell the tooltip that is waiting
+ // for this information.
+ let nick = aMessage.params[1];
+
+ if (this.whoisInformation.has(nick)) {
+ this.notifyWhois(nick);
+ } else {
+ // If there is no whois information stored at this point, the nick
+ // is either offline or does not exist, so we run WHOWAS.
+ this.requestOfflineBuddyInfo(nick);
+ }
+ return true;
+ },
+ 319(aMessage) {
+ // RPL_WHOISCHANNELS
+ // <nick> :*( ( "@" / "+" ) <channel> " " )
+ return this.setWhois(aMessage.params[1], {
+ channels: aMessage.params[2],
+ });
+ },
+
+ /*
+ * LIST
+ */
+ 321(aMessage) {
+ // RPL_LISTSTART
+ // Channel :Users Name
+ // Obsolete. Not used.
+ return true;
+ },
+ 322(aMessage) {
+ // RPL_LIST
+ // <channel> <# visible> :<topic>
+ let name = aMessage.params[1];
+ let participantCount = aMessage.params[2];
+ let topic = aMessage.params[3];
+ // Some servers (e.g. Unreal) include the channel's modes before the topic.
+ // Omit this.
+ topic = topic.replace(/^\[\+[a-zA-Z]*\] /, "");
+ // Force the allocation of a new copy of the string so as to prevent
+ // the JS engine from retaining the whole original socket string. See bug
+ // 1058584. This hack can be removed when bug 1058653 is fixed.
+ topic = topic ? topic.normalize() : "";
+
+ this._channelList.set(name, { topic, participantCount });
+ this._currentBatch.push(name);
+ // Give callbacks a batch of channels of length _channelsPerBatch.
+ if (this._currentBatch.length == this._channelsPerBatch) {
+ for (let callback of this._roomInfoCallbacks) {
+ callback.onRoomInfoAvailable(this._currentBatch, false);
+ }
+ this._currentBatch = [];
+ }
+ return true;
+ },
+ 323(aMessage) {
+ // RPL_LISTEND
+ // :End of LIST
+ this._sendRemainingRoomInfo();
+ return true;
+ },
+
+ /*
+ * Channel functions
+ */
+ 324(aMessage) {
+ // RPL_CHANNELMODEIS
+ // <channel> <mode> <mode params>
+ this.getConversation(aMessage.params[1]).setMode(
+ aMessage.params[2],
+ aMessage.params.slice(3),
+ aMessage.origin
+ );
+
+ return true;
+ },
+ 325(aMessage) {
+ // RPL_UNIQOPIS
+ // <channel> <nickname>
+ // TODO parse this and have the UI respond accordingly.
+ return false;
+ },
+ 331(aMessage) {
+ // RPL_NOTOPIC
+ // <channel> :No topic is set
+ let conversation = this.getConversation(aMessage.params[1]);
+ // Clear the topic.
+ conversation.setTopic("");
+ return true;
+ },
+ 332(aMessage) {
+ // RPL_TOPIC
+ // <channel> :<topic>
+ // Update the topic UI
+ let conversation = this.getConversation(aMessage.params[1]);
+ let topic = aMessage.params[2];
+ conversation.setTopic(topic ? ctcpFormatToText(topic) : "");
+ return true;
+ },
+ 333(aMessage) {
+ // nonstandard
+ // <channel> <nickname> <time>
+ return true;
+ },
+
+ /*
+ * Invitations
+ */
+ 341(aMessage) {
+ // RPL_INVITING
+ // <channel> <nick>
+ // Note that servers reply with parameters in the reverse order from the
+ // above (which is as specified by RFC 2812).
+ this.getConversation(aMessage.params[2]).writeMessage(
+ aMessage.origin,
+ lazy._("message.invited", aMessage.params[1], aMessage.params[2]),
+ { system: true }
+ );
+ return true;
+ },
+ 342(aMessage) {
+ // RPL_SUMMONING
+ // <user> :Summoning user to IRC
+ return writeMessage(
+ this,
+ aMessage,
+ lazy._("message.summoned", aMessage.params[0])
+ );
+ },
+ 346(aMessage) {
+ // RPL_INVITELIST
+ // <channel> <invitemask>
+ // TODO what do we do?
+ return false;
+ },
+ 347(aMessage) {
+ // RPL_ENDOFINVITELIST
+ // <channel> :End of channel invite list
+ // TODO what do we do?
+ return false;
+ },
+ 348(aMessage) {
+ // RPL_EXCEPTLIST
+ // <channel> <exceptionmask>
+ // TODO what do we do?
+ return false;
+ },
+ 349(aMessage) {
+ // RPL_ENDOFEXCEPTIONLIST
+ // <channel> :End of channel exception list
+ // TODO update UI?
+ return false;
+ },
+
+ /*
+ * Version
+ */
+ 351(aMessage) {
+ // RPL_VERSION
+ // <version>.<debuglevel> <server> :<comments>
+ return serverMessage(this, aMessage);
+ },
+
+ /*
+ * WHO
+ */
+ 352(aMessage) {
+ // RPL_WHOREPLY
+ // <channel> <user> <host> <server> <nick> ( "H" / "G" ) ["*"] [ ("@" / "+" ) ] :<hopcount> <real name>
+ // TODO parse and display this?
+ return false;
+ },
+
+ /*
+ * NAMREPLY
+ */
+ 353(aMessage) {
+ // RPL_NAMREPLY
+ // <target> ( "=" / "*" / "@" ) <channel> :[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
+ let conversation = this.getConversation(aMessage.params[2]);
+ // Keep if this is secret (@), private (*) or public (=).
+ conversation.setModesFromRestriction(aMessage.params[1]);
+ // Add the participants.
+ let newParticipants = [];
+ aMessage.params[3]
+ .trim()
+ .split(" ")
+ .forEach(aNick =>
+ newParticipants.push(conversation.getParticipant(aNick, false))
+ );
+ conversation.notifyObservers(
+ new nsSimpleEnumerator(newParticipants),
+ "chat-buddy-add"
+ );
+ return true;
+ },
+
+ 361(aMessage) {
+ // RPL_KILLDONE
+ // Non-generic
+ // TODO What is this?
+ return false;
+ },
+ 362(aMessage) {
+ // RPL_CLOSING
+ // Non-generic
+ // TODO What is this?
+ return false;
+ },
+ 363(aMessage) {
+ // RPL_CLOSEEND
+ // Non-generic
+ // TODO What is this?
+ return false;
+ },
+
+ /*
+ * Links.
+ */
+ 364(aMessage) {
+ // RPL_LINKS
+ // <mask> <server> :<hopcount> <server info>
+ return serverMessage(this, aMessage);
+ },
+ 365(aMessage) {
+ // RPL_ENDOFLINKS
+ // <mask> :End of LINKS list
+ return true;
+ },
+
+ /*
+ * Names
+ */
+ 366(aMessage) {
+ // RPL_ENDOFNAMES
+ // <target> <channel> :End of NAMES list
+ // All participants have already been added by the 353 handler.
+
+ // This assumes that this is the last message received when joining a
+ // channel, so a few "clean up" tasks are done here.
+ let conversation = this.getConversation(aMessage.params[1]);
+
+ // Update the topic as we may have added the participant for
+ // the user after the mode message was handled, and so
+ // topicSettable may have changed.
+ conversation.notifyObservers(this, "chat-update-topic");
+
+ // If we haven't received the MODE yet, request it.
+ if (!conversation._receivedInitialMode) {
+ this.sendMessage("MODE", aMessage.params[1]);
+ }
+
+ return true;
+ },
+ /*
+ * End of a bunch of lists
+ */
+ 367(aMessage) {
+ // RPL_BANLIST
+ // <channel> <banmask>
+ let conv = this.getConversation(aMessage.params[1]);
+ if (!conv.banMasks.includes(aMessage.params[2])) {
+ conv.banMasks.push(aMessage.params[2]);
+ }
+ return true;
+ },
+ 368(aMessage) {
+ // RPL_ENDOFBANLIST
+ // <channel> :End of channel ban list
+ let conv = this.getConversation(aMessage.params[1]);
+ let msg;
+ if (conv.banMasks.length) {
+ msg = [lazy._("message.banMasks", aMessage.params[1])]
+ .concat(conv.banMasks)
+ .join("\n");
+ } else {
+ msg = lazy._("message.noBanMasks", aMessage.params[1]);
+ }
+ conv.writeMessage(aMessage.origin, msg, { system: true });
+ return true;
+ },
+ 369(aMessage) {
+ // RPL_ENDOFWHOWAS
+ // <nick> :End of WHOWAS
+ // We've received everything about WHOWAS, tell the tooltip that is waiting
+ // for this information.
+ this.notifyWhois(aMessage.params[1]);
+ return true;
+ },
+
+ /*
+ * Server info
+ */
+ 371(aMessage) {
+ // RPL_INFO
+ // :<string>
+ return serverMessage(this, aMessage);
+ },
+ 372(aMessage) {
+ // RPL_MOTD
+ // :- <text>
+ return addMotd(this, aMessage);
+ },
+ 373(aMessage) {
+ // RPL_INFOSTART
+ // Non-generic
+ // This is unnecessary and servers just send RPL_INFO.
+ return true;
+ },
+ 374(aMessage) {
+ // RPL_ENDOFINFO
+ // :End of INFO list
+ return true;
+ },
+ 375(aMessage) {
+ // RPL_MOTDSTART
+ // :- <server> Message of the day -
+ return addMotd(this, aMessage);
+ },
+ 376(aMessage) {
+ // RPL_ENDOFMOTD
+ // :End of MOTD command
+ // Show the MOTD if the user wants to see server messages or if
+ // RPL_WELCOME has not been received since some servers (e.g. irc.ppy.sh)
+ // use this as a CAPTCHA like mechanism before login can occur.
+ if (this._showServerTab || !this.connected) {
+ writeMessage(this, aMessage, this._motd.join("\n"), "incoming");
+ }
+ // No reason to keep the MOTD in memory.
+ delete this._motd;
+ // Clear the MOTD timer.
+ clearTimeout(this._motdTimer);
+ delete this._motdTimer;
+
+ return true;
+ },
+
+ /*
+ * OPER
+ */
+ 381(aMessage) {
+ // RPL_YOUREOPER
+ // :You are now an IRC operator
+ // TODO update UI accordingly to show oper status
+ return serverMessage(this, aMessage);
+ },
+ 382(aMessage) {
+ // RPL_REHASHING
+ // <config file> :Rehashing
+ return serverMessage(this, aMessage);
+ },
+ 383(aMessage) {
+ // RPL_YOURESERVICE
+ // You are service <servicename>
+ this.WARN('Received "You are a service" message.');
+ return true;
+ },
+
+ /*
+ * Info
+ */
+ 384(aMessage) {
+ // RPL_MYPORTIS
+ // Non-generic
+ // TODO Parse and display?
+ return false;
+ },
+ 391(aMessage) {
+ // RPL_TIME
+ // <server> :<string showing server's local time>
+
+ let msg = lazy._("ctcp.time", aMessage.params[1], aMessage.params[2]);
+ // Show the date returned from the server, note that this doesn't use
+ // the serverMessage function: since this is in response to a command, it
+ // should always be shown.
+ return writeMessage(this, aMessage, msg, "system");
+ },
+ 392(aMessage) {
+ // RPL_USERSSTART
+ // :UserID Terminal Host
+ // TODO
+ return false;
+ },
+ 393(aMessage) {
+ // RPL_USERS
+ // :<username> <ttyline> <hostname>
+ // TODO store into buddy list? List out?
+ return false;
+ },
+ 394(aMessage) {
+ // RPL_ENDOFUSERS
+ // :End of users
+ // TODO Notify observers of the buddy list?
+ return false;
+ },
+ 395(aMessage) {
+ // RPL_NOUSERS
+ // :Nobody logged in
+ // TODO clear buddy list?
+ return false;
+ },
+
+ // Error messages, Implement Section 5.2 of RFC 2812
+ 401(aMessage) {
+ // ERR_NOSUCHNICK
+ // <nickname> :No such nick/channel
+ // Can arise in response to /mode, /invite, /kill, /msg, /whois.
+ // TODO Handled in the conversation for /whois and /mgs so far.
+ let msgId =
+ "error.noSuch" +
+ (this.isMUCName(aMessage.params[1]) ? "Channel" : "Nick");
+ if (this.conversations.has(aMessage.params[1])) {
+ // If the conversation exists and we just sent a message from it, then
+ // notify that the user is offline.
+ if (this.getConversation(aMessage.params[1])._pendingMessage) {
+ conversationErrorMessage(this, aMessage, msgId);
+ }
+ }
+
+ return serverErrorMessage(
+ this,
+ aMessage,
+ lazy._(msgId, aMessage.params[1])
+ );
+ },
+ 402(aMessage) {
+ // ERR_NOSUCHSERVER
+ // <server name> :No such server
+ // TODO Parse & display an error to the user.
+ return false;
+ },
+ 403(aMessage) {
+ // ERR_NOSUCHCHANNEL
+ // <channel name> :No such channel
+ return conversationErrorMessage(
+ this,
+ aMessage,
+ "error.noChannel",
+ true,
+ false
+ );
+ },
+ 404(aMessage) {
+ // ERR_CANNOTSENDTOCHAN
+ // <channel name> :Cannot send to channel
+ // Notify the user that they can't send to that channel.
+ return conversationErrorMessage(
+ this,
+ aMessage,
+ "error.cannotSendToChannel"
+ );
+ },
+ 405(aMessage) {
+ // ERR_TOOMANYCHANNELS
+ // <channel name> :You have joined too many channels
+ return conversationErrorMessage(
+ this,
+ aMessage,
+ "error.tooManyChannels",
+ true
+ );
+ },
+ 406(aMessage) {
+ // ERR_WASNOSUCHNICK
+ // <nickname> :There was no such nickname
+ // Can arise in response to WHOWAS.
+ return serverErrorMessage(
+ this,
+ aMessage,
+ lazy._("error.wasNoSuchNick", aMessage.params[1])
+ );
+ },
+ 407(aMessage) {
+ // ERR_TOOMANYTARGETS
+ // <target> :<error code> recipients. <abort message>
+ return conversationErrorMessage(
+ this,
+ aMessage,
+ "error.nonUniqueTarget",
+ false,
+ false
+ );
+ },
+ 408(aMessage) {
+ // ERR_NOSUCHSERVICE
+ // <service name> :No such service
+ // TODO
+ return false;
+ },
+ 409(aMessage) {
+ // ERR_NOORIGIN
+ // :No origin specified
+ // TODO failed PING/PONG message, this should never occur?
+ return false;
+ },
+ 411(aMessage) {
+ // ERR_NORECIPIENT
+ // :No recipient given (<command>)
+ // If this happens a real error with the protocol occurred.
+ this.ERROR("ERR_NORECIPIENT: No recipient given for PRIVMSG.");
+ return true;
+ },
+ 412(aMessage) {
+ // ERR_NOTEXTTOSEND
+ // :No text to send
+ // If this happens a real error with the protocol occurred: we should
+ // always block the user from sending empty messages.
+ this.ERROR("ERR_NOTEXTTOSEND: No text to send for PRIVMSG.");
+ return true;
+ },
+ 413(aMessage) {
+ // ERR_NOTOPLEVEL
+ // <mask> :No toplevel domain specified
+ // If this response is received, a real error occurred in the protocol.
+ this.ERROR("ERR_NOTOPLEVEL: Toplevel domain not specified.");
+ return true;
+ },
+ 414(aMessage) {
+ // ERR_WILDTOPLEVEL
+ // <mask> :Wildcard in toplevel domain
+ // If this response is received, a real error occurred in the protocol.
+ this.ERROR("ERR_WILDTOPLEVEL: Wildcard toplevel domain specified.");
+ return true;
+ },
+ 415(aMessage) {
+ // ERR_BADMASK
+ // <mask> :Bad Server/host mask
+ // If this response is received, a real error occurred in the protocol.
+ this.ERROR("ERR_BADMASK: Bad server/host mask specified.");
+ return true;
+ },
+ 421(aMessage) {
+ // ERR_UNKNOWNCOMMAND
+ // <command> :Unknown command
+ // TODO This shouldn't occur.
+ return false;
+ },
+ 422(aMessage) {
+ // ERR_NOMOTD
+ // :MOTD File is missing
+ // No message of the day to display.
+ return true;
+ },
+ 423(aMessage) {
+ // ERR_NOADMININFO
+ // <server> :No administrative info available
+ // TODO
+ return false;
+ },
+ 424(aMessage) {
+ // ERR_FILEERROR
+ // :File error doing <file op> on <file>
+ // TODO
+ return false;
+ },
+ 431(aMessage) {
+ // ERR_NONICKNAMEGIVEN
+ // :No nickname given
+ // TODO
+ return false;
+ },
+ 432(aMessage) {
+ // ERR_ERRONEUSNICKNAME
+ // <nick> :Erroneous nickname
+ let msg = lazy._("error.erroneousNickname", this._requestedNickname);
+ serverErrorMessage(this, aMessage, msg);
+ if (this._requestedNickname == this._accountNickname) {
+ // The account has been set up with an illegal nickname.
+ this.ERROR(
+ "Erroneous nickname " +
+ this._requestedNickname +
+ ": " +
+ aMessage.params.slice(1).join(" ")
+ );
+ this.gotDisconnected(Ci.prplIAccount.ERROR_INVALID_USERNAME, msg);
+ } else {
+ // Reset original nickname to the account nickname in case of
+ // later reconnections.
+ this._requestedNickname = this._accountNickname;
+ }
+ return true;
+ },
+ 433(aMessage) {
+ // ERR_NICKNAMEINUSE
+ // <nick> :Nickname is already in use
+ // Try to get the desired nick back in 2.5 minutes if this happens when
+ // connecting, in case it was just due to the user's nick not having
+ // timed out yet on the server.
+ if (this.connecting && aMessage.params[1] == this._requestedNickname) {
+ this._nickInUseTimeout = setTimeout(() => {
+ this.changeNick(this._requestedNickname);
+ delete this._nickInUseTimeout;
+ }, 150000);
+ }
+ return this.tryNewNick(aMessage.params[1]);
+ },
+ 436(aMessage) {
+ // ERR_NICKCOLLISION
+ // <nick> :Nickname collision KILL from <user>@<host>
+ return this.tryNewNick(aMessage.params[1]);
+ },
+ 437(aMessage) {
+ // ERR_UNAVAILRESOURCE
+ // <nick/channel> :Nick/channel is temporarily unavailable
+ return conversationErrorMessage(
+ this,
+ aMessage,
+ "error.unavailable",
+ true
+ );
+ },
+ 441(aMessage) {
+ // ERR_USERNOTINCHANNEL
+ // <nick> <channel> :They aren't on that channel
+ // TODO
+ return false;
+ },
+ 442(aMessage) {
+ // ERR_NOTONCHANNEL
+ // <channel> :You're not on that channel
+ this.ERROR(
+ "A command affecting " +
+ aMessage.params[1] +
+ " failed because you aren't in that channel."
+ );
+ return true;
+ },
+ 443(aMessage) {
+ // ERR_USERONCHANNEL
+ // <user> <channel> :is already on channel
+ this.getConversation(aMessage.params[2]).writeMessage(
+ aMessage.origin,
+ lazy._(
+ "message.alreadyInChannel",
+ aMessage.params[1],
+ aMessage.params[2]
+ ),
+ { system: true }
+ );
+ return true;
+ },
+ 444(aMessage) {
+ // ERR_NOLOGIN
+ // <user> :User not logged in
+ // TODO
+ return false;
+ },
+ 445(aMessage) {
+ // ERR_SUMMONDISABLED
+ // :SUMMON has been disabled
+ // TODO keep track of this and disable UI associated?
+ return false;
+ },
+ 446(aMessage) {
+ // ERR_USERSDISABLED
+ // :USERS has been disabled
+ // TODO Disabled all buddy list etc.
+ return false;
+ },
+ 451(aMessage) {
+ // ERR_NOTREGISTERED
+ // :You have not registered
+ // If the server doesn't understand CAP it might return this error.
+ if (aMessage.params[0] == "CAP") {
+ this.LOG("Server doesn't support CAP.");
+ return true;
+ }
+ // TODO
+ return false;
+ },
+ 461(aMessage) {
+ // ERR_NEEDMOREPARAMS
+ // <command> :Not enough parameters
+
+ if (!this.connected) {
+ // The account has been set up with an illegal username.
+ this.ERROR("Erroneous username: " + this.username);
+ this.gotDisconnected(
+ Ci.prplIAccount.ERROR_INVALID_USERNAME,
+ lazy._("connection.error.invalidUsername", this.user)
+ );
+ return true;
+ }
+
+ return false;
+ },
+ 462(aMessage) {
+ // ERR_ALREADYREGISTERED
+ // :Unauthorized command (already registered)
+ // TODO
+ return false;
+ },
+ 463(aMessage) {
+ // ERR_NOPERMFORHOST
+ // :Your host isn't among the privileged
+ // TODO
+ return false;
+ },
+ 464(aMessage) {
+ // ERR_PASSWDMISMATCH
+ // :Password incorrect
+ this.gotDisconnected(
+ Ci.prplIAccount.ERROR_AUTHENTICATION_FAILED,
+ lazy._("connection.error.invalidPassword")
+ );
+ return true;
+ },
+ 465(aMessage) {
+ // ERR_YOUREBANEDCREEP
+ // :You are banned from this server
+ serverErrorMessage(this, aMessage, lazy._("error.banned"));
+ this.gotDisconnected(
+ Ci.prplIAccount.ERROR_OTHER_ERROR,
+ lazy._("error.banned")
+ ); // Notify account manager.
+ return true;
+ },
+ 466(aMessage) {
+ // ERR_YOUWILLBEBANNED
+ return serverErrorMessage(this, aMessage, lazy._("error.bannedSoon"));
+ },
+ 467(aMessage) {
+ // ERR_KEYSET
+ // <channel> :Channel key already set
+ // TODO
+ return false;
+ },
+ 471(aMessage) {
+ // ERR_CHANNELISFULL
+ // <channel> :Cannot join channel (+l)
+ return conversationErrorMessage(
+ this,
+ aMessage,
+ "error.channelFull",
+ true
+ );
+ },
+ 472(aMessage) {
+ // ERR_UNKNOWNMODE
+ // <char> :is unknown mode char to me for <channel>
+ // TODO
+ return false;
+ },
+ 473(aMessage) {
+ // ERR_INVITEONLYCHAN
+ // <channel> :Cannot join channel (+i)
+ return conversationErrorMessage(
+ this,
+ aMessage,
+ "error.inviteOnly",
+ true,
+ false
+ );
+ },
+ 474(aMessage) {
+ // ERR_BANNEDFROMCHAN
+ // <channel> :Cannot join channel (+b)
+ return conversationErrorMessage(
+ this,
+ aMessage,
+ "error.channelBanned",
+ true,
+ false
+ );
+ },
+ 475(aMessage) {
+ // ERR_BADCHANNELKEY
+ // <channel> :Cannot join channel (+k)
+ return conversationErrorMessage(
+ this,
+ aMessage,
+ "error.wrongKey",
+ true,
+ false
+ );
+ },
+ 476(aMessage) {
+ // ERR_BADCHANMASK
+ // <channel> :Bad Channel Mask
+ // TODO
+ return false;
+ },
+ 477(aMessage) {
+ // ERR_NOCHANMODES
+ // <channel> :Channel doesn't support modes
+ // TODO
+ return false;
+ },
+ 478(aMessage) {
+ // ERR_BANLISTFULL
+ // <channel> <char> :Channel list is full
+ // TODO
+ return false;
+ },
+ 481(aMessage) {
+ // ERR_NOPRIVILEGES
+ // :Permission Denied- You're not an IRC operator
+ // TODO ask to auth?
+ return false;
+ },
+ 482(aMessage) {
+ // ERR_CHANOPRIVSNEEDED
+ // <channel> :You're not channel operator
+ return conversationErrorMessage(this, aMessage, "error.notChannelOp");
+ },
+ 483(aMessage) {
+ // ERR_CANTKILLSERVER
+ // :You can't kill a server!
+ // TODO Display error?
+ return false;
+ },
+ 484(aMessage) {
+ // ERR_RESTRICTED
+ // :Your connection is restricted!
+ // Indicates user mode +r
+ // TODO
+ return false;
+ },
+ 485(aMessage) {
+ // ERR_UNIQOPPRIVSNEEDED
+ // :You're not the original channel operator
+ // TODO ask to auth?
+ return false;
+ },
+ 491(aMessage) {
+ // ERR_NOOPERHOST
+ // :No O-lines for your host
+ // TODO
+ return false;
+ },
+ 492(aMessage) {
+ // ERR_NOSERVICEHOST
+ // Non-generic
+ // TODO
+ return false;
+ },
+ 501(aMessage) {
+ // ERR_UMODEUNKNOWNFLAGS
+ // :Unknown MODE flag
+ return serverErrorMessage(
+ this,
+ aMessage,
+ lazy._("error.unknownMode", aMessage.params[1])
+ );
+ },
+ 502(aMessage) {
+ // ERR_USERSDONTMATCH
+ // :Cannot change mode for other users
+ return serverErrorMessage(this, aMessage, lazy._("error.mode.wrongUser"));
+ },
+ },
+};