summaryrefslogtreecommitdiffstats
path: root/comm/chat/protocols/irc/ircNonStandard.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'comm/chat/protocols/irc/ircNonStandard.sys.mjs')
-rw-r--r--comm/chat/protocols/irc/ircNonStandard.sys.mjs262
1 files changed, 262 insertions, 0 deletions
diff --git a/comm/chat/protocols/irc/ircNonStandard.sys.mjs b/comm/chat/protocols/irc/ircNonStandard.sys.mjs
new file mode 100644
index 0000000000..aeb373feb9
--- /dev/null
+++ b/comm/chat/protocols/irc/ircNonStandard.sys.mjs
@@ -0,0 +1,262 @@
+/* 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/. */
+
+/*
+ * There are a variety of non-standard extensions to IRC that are implemented by
+ * different servers. This implementation is based on a combination of
+ * documentation and reverse engineering. Each handler must include a comment
+ * listing the known servers that support this extension.
+ *
+ * Resources for these commands include:
+ * https://github.com/atheme/charybdis/blob/master/include/numeric.h
+ * https://github.com/unrealircd/unrealircd/blob/unreal42/include/numeric.h
+ */
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+import { l10nHelper } from "resource:///modules/imXPCOMUtils.sys.mjs";
+import { ircHandlerPriorities } from "resource:///modules/ircHandlerPriorities.sys.mjs";
+import {
+ conversationErrorMessage,
+ kListRefreshInterval,
+} from "resource:///modules/ircUtils.sys.mjs";
+
+const lazy = {};
+XPCOMUtils.defineLazyGetter(lazy, "_", () =>
+ l10nHelper("chrome://chat/locale/irc.properties")
+);
+
+export var ircNonStandard = {
+ name: "Non-Standard IRC Extensions",
+ priority: ircHandlerPriorities.DEFAULT_PRIORITY + 1,
+ isEnabled: () => true,
+
+ commands: {
+ NOTICE(aMessage) {
+ // NOTICE <msgtarget> <text>
+
+ if (
+ aMessage.params[1].startsWith("*** You cannot list within the first")
+ ) {
+ // SECURELIST: "You cannot list within the first N seconds of connecting.
+ // Please try again later." This NOTICE will be followed by a 321/323
+ // pair, but no list data.
+ // We fake the last LIST time so that we will retry LIST the next time
+ // the user requires it after the interval specified.
+ const kMinute = 60000;
+ let waitTime = aMessage.params[1].split(" ")[7] * 1000 || kMinute;
+ this._lastListTime = Date.now() + waitTime - kListRefreshInterval;
+ return true;
+ }
+
+ // If the user is connected, fallback to normal processing, everything
+ // past this points deals with NOTICE messages that occur before 001 is
+ // received.
+ if (this.connected) {
+ return false;
+ }
+
+ let target = aMessage.params[0].toLowerCase();
+
+ // If we receive a ZNC error message requesting a password, the
+ // serverPassword preference was not set by the user. Attempt to log into
+ // ZNC using the account password.
+ if (
+ target == "auth" &&
+ aMessage.params[1].startsWith("*** You need to send your password.")
+ ) {
+ if (this.imAccount.password) {
+ // Send the password now, if it is available.
+ this.shouldAuthenticate = false;
+ this.sendMessage(
+ "PASS",
+ this.imAccount.password,
+ "PASS <password not logged>"
+ );
+ } else {
+ // Otherwise, put the account in an error state.
+ this.gotDisconnected(
+ Ci.prplIAccount.ERROR_AUTHENTICATION_IMPOSSIBLE,
+ lazy._("connection.error.passwordRequired")
+ );
+ }
+
+ // All done for ZNC.
+ return true;
+ }
+
+ // Some servers, e.g. irc.umich.edu, use NOTICE during connection
+ // negotiation to give directions to users, these MUST be shown to the
+ // user. If the message starts with ***, we assume it is probably an AUTH
+ // message, which falls through to normal NOTICE processing.
+ // Note that if the user's nick is auth this COULD be a notice directed at
+ // them. For reference: moznet sends Auth (previously sent AUTH), freenode
+ // sends *.
+ let isAuth = target == "auth" && this._nickname.toLowerCase() != "auth";
+ if (!aMessage.params[1].startsWith("***") && !isAuth) {
+ this.getConversation(aMessage.origin).writeMessage(
+ aMessage.origin,
+ aMessage.params[1],
+ {
+ incoming: true,
+ tags: aMessage.tags,
+ }
+ );
+ return true;
+ }
+
+ return false;
+ },
+
+ "042": function (aMessage) {
+ // RPL_YOURID (IRCnet)
+ // <nick> <id> :your unique ID
+ return true;
+ },
+
+ 307(aMessage) {
+ // TODO RPL_SUSERHOST (AustHex)
+ // TODO RPL_USERIP (Undernet)
+ // <user ips>
+
+ // RPL_WHOISREGNICK (Unreal & Bahamut)
+ // <nick> :is a registered nick
+ if (aMessage.params.length == 3) {
+ return this.setWhois(aMessage.params[1], { registered: true });
+ }
+
+ return false;
+ },
+
+ 317(aMessage) {
+ // RPL_WHOISIDLE (Unreal & Charybdis)
+ // <nick> <integer> <integer> :seconds idle, signon time
+ // This is a non-standard extension to RPL_WHOISIDLE which includes the
+ // sign-on time.
+ if (aMessage.params.length == 5) {
+ this.setWhois(aMessage.params[1], { signonTime: aMessage.params[3] });
+ }
+
+ return false;
+ },
+
+ 328(aMessage) {
+ // RPL_CHANNEL_URL (Bahamut & Austhex)
+ // <channel> :<URL>
+ return true;
+ },
+
+ 329(aMessage) {
+ // RPL_CREATIONTIME (Bahamut & Unreal)
+ // <channel> <creation time>
+ return true;
+ },
+
+ 330(aMessage) {
+ // TODO RPL_WHOWAS_TIME
+
+ // RPL_WHOISACCOUNT (Charybdis, ircu & Quakenet)
+ // <nick> <authname> :is logged in as
+ if (aMessage.params.length == 4) {
+ let [, nick, authname] = aMessage.params;
+ // If the authname differs from the nickname, add it to the WHOIS
+ // information; otherwise, ignore it.
+ if (this.normalize(nick) != this.normalize(authname)) {
+ this.setWhois(nick, { registeredAs: authname });
+ }
+ }
+ return true;
+ },
+
+ 335(aMessage) {
+ // RPL_WHOISBOT (Unreal)
+ // <nick> :is a \002Bot\002 on <network>
+ return this.setWhois(aMessage.params[1], { bot: true });
+ },
+
+ 338(aMessage) {
+ // RPL_CHANPASSOK
+ // RPL_WHOISACTUALLY (ircu, Bahamut, Charybdis)
+ // <nick> <user> <ip> :actually using host
+ return true;
+ },
+
+ 378(aMessage) {
+ // RPL_WHOISHOST (Unreal & Charybdis)
+ // <nick> :is connecting from <host> <ip>
+ let [host, ip] = aMessage.params[2].split(" ").slice(-2);
+ return this.setWhois(aMessage.params[1], { host, ip });
+ },
+
+ 379(aMessage) {
+ // RPL_WHOISMODES (Unreal, Inspircd)
+ // <nick> :is using modes <modes>
+ // Sent in response to a WHOIS on the user.
+ return true;
+ },
+
+ 396(aMessage) {
+ // RPL_HOSTHIDDEN (Charybdis, Hybrid, ircu, etc.)
+ // RPL_VISIBLEHOST (Plexus)
+ // RPL_YOURDISPLAYEDHOST (Inspircd)
+ // <host> :is now your hidden host
+
+ // This is the host that will be sent to other users.
+ this.prefix = "!" + aMessage.user + "@" + aMessage.params[1];
+ return true;
+ },
+
+ 464(aMessage) {
+ // :Password required
+ // If we receive a ZNC error message requesting a password, eat it since
+ // a NOTICE AUTH will follow causing us to send the password. This numeric
+ // is, unfortunately, also sent if you give a wrong password. The
+ // parameter in that case is "Invalid Password".
+ return (
+ aMessage.origin == "irc.znc.in" &&
+ aMessage.params[1] == "Password required"
+ );
+ },
+
+ 470(aMessage) {
+ // Channel forward (Unreal, inspircd)
+ // <requested channel> <redirect channel>: You may not join this channel,
+ // so you are automatically being transferred to the redirect channel.
+ // Join redirect channel so when the automatic join happens, we are
+ // not surprised.
+ this.joinChat(this.getChatRoomDefaultFieldValues(aMessage.params[2]));
+ // Mark requested channel as left and add a system message.
+ return conversationErrorMessage(
+ this,
+ aMessage,
+ "error.channelForward",
+ true,
+ false
+ );
+ },
+
+ 499(aMessage) {
+ // ERR_CHANOWNPRIVNEEDED (Unreal)
+ // <channel> :You're not the channel owner (status +q is needed)
+ return conversationErrorMessage(this, aMessage, "error.notChannelOwner");
+ },
+
+ 671(aMessage) {
+ // RPL_WHOISSECURE (Unreal & Charybdis)
+ // <nick> :is using a Secure connection
+ return this.setWhois(aMessage.params[1], { secure: true });
+ },
+
+ 998(aMessage) {
+ // irc.umich.edu shows an ASCII captcha that must be typed in by the user.
+ this.getConversation(aMessage.origin).writeMessage(
+ aMessage.origin,
+ aMessage.params[1],
+ {
+ incoming: true,
+ noFormat: true,
+ }
+ );
+ return true;
+ },
+ },
+};