summaryrefslogtreecommitdiffstats
path: root/comm/chat/protocols/irc/ircNonStandard.sys.mjs
blob: aeb373feb94b6fad48fad07651e4b925b1eafd0d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
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;
    },
  },
};