summaryrefslogtreecommitdiffstats
path: root/comm/chat/protocols/irc/ircWatchMonitor.sys.mjs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/chat/protocols/irc/ircWatchMonitor.sys.mjs
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/chat/protocols/irc/ircWatchMonitor.sys.mjs')
-rw-r--r--comm/chat/protocols/irc/ircWatchMonitor.sys.mjs467
1 files changed, 467 insertions, 0 deletions
diff --git a/comm/chat/protocols/irc/ircWatchMonitor.sys.mjs b/comm/chat/protocols/irc/ircWatchMonitor.sys.mjs
new file mode 100644
index 0000000000..c7bdb2bf7b
--- /dev/null
+++ b/comm/chat/protocols/irc/ircWatchMonitor.sys.mjs
@@ -0,0 +1,467 @@
+/* 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 implements the WATCH and MONITOR commands: ways to more efficiently
+ * (compared to ISON) keep track of a user's status.
+ *
+ * MONITOR (supported by Charybdis)
+ * https://github.com/atheme/charybdis/blob/master/doc/monitor.txt
+ * WATCH (supported by Bahamut and UnrealIRCd)
+ * http://www.stack.nl/~jilles/cgi-bin/hgwebdir.cgi/irc-documentation-jilles/raw-file/tip/reference/draft-meglio-irc-watch-00.txt
+ */
+
+import { clearTimeout } from "resource://gre/modules/Timer.sys.mjs";
+import { ircHandlerPriorities } from "resource:///modules/ircHandlerPriorities.sys.mjs";
+
+function setStatus(aAccount, aNick, aStatus) {
+ if (!aAccount.watchEnabled && !aAccount.monitorEnabled) {
+ return false;
+ }
+
+ if (aStatus == "AWAY") {
+ // We need to request the away message.
+ aAccount.requestCurrentWhois(aNick);
+ } else {
+ // Clear the WHOIS information.
+ aAccount.removeBuddyInfo(aNick);
+ }
+
+ let buddy = aAccount.buddies.get(aNick);
+ if (!buddy) {
+ return false;
+ }
+ buddy.setStatus(Ci.imIStatusInfo["STATUS_" + aStatus], "");
+ return true;
+}
+
+function trackBuddyWatch(aNicks) {
+ // aNicks is an array when WATCH is initialized, and a single nick
+ // in all later calls.
+ if (!Array.isArray(aNicks)) {
+ // We update the trackQueue if an individual nick is being added,
+ // so the nick will also be monitored after a reconnect.
+ Object.getPrototypeOf(this).trackBuddy.call(this, aNicks);
+ aNicks = [aNicks];
+ }
+
+ let nicks = aNicks.map(aNick => "+" + aNick);
+ if (!nicks.length) {
+ return;
+ }
+
+ let newWatchLength = this.watchLength + nicks.length;
+ if (newWatchLength > this.maxWatchLength) {
+ this.WARN(
+ "Attempting to WATCH " +
+ newWatchLength +
+ " nicks; maximum size is " +
+ this.maxWatchLength +
+ "."
+ );
+ // TODO We should trim the list and add the extra users to an ISON queue,
+ // but that's not currently implemented, so just hope the server doesn't
+ // enforce it's own limit.
+ }
+ this.watchLength = newWatchLength;
+
+ // Watch away as well as online.
+ let params = [];
+ if (this.watchAwayEnabled) {
+ params.push("A");
+ }
+ let maxLength =
+ this.maxMessageLength -
+ 2 -
+ this.countBytes(this.buildMessage("WATCH", params));
+ for (let nick of nicks) {
+ if (this.countBytes(params + " " + nick) >= maxLength) {
+ // If the message would be too long, first send this message.
+ this.sendMessage("WATCH", params);
+ // Reset for the next message.
+ params = [];
+ if (this.watchAwayEnabled) {
+ params.push("A");
+ }
+ }
+ params.push(nick);
+ }
+ this.sendMessage("WATCH", params);
+}
+function untrackBuddyWatch(aNick) {
+ --this.watchLength;
+ this.sendMessage("WATCH", "-" + aNick);
+ Object.getPrototypeOf(this).untrackBuddy.call(this, aNick);
+}
+
+export var isupportWATCH = {
+ name: "WATCH",
+ // Slightly above default ISUPPORT priority.
+ priority: ircHandlerPriorities.DEFAULT_PRIORITY + 10,
+ isEnabled: () => true,
+
+ commands: {
+ WATCH(aMessage) {
+ if (!aMessage.isupport.useDefault) {
+ this.maxWatchLength = 128;
+ } else {
+ let size = parseInt(aMessage.isupport.value, 10);
+ if (isNaN(size)) {
+ return false;
+ }
+ this.maxWatchLength = size;
+ }
+
+ this.watchEnabled = true;
+
+ // Clear our watchlist in case there is garbage in it.
+ this.sendMessage("WATCH", "C");
+ this.watchLength = 0;
+
+ // Kill the ISON polling loop.
+ clearTimeout(this._isOnTimer);
+
+ return true;
+ },
+
+ WATCHOPTS(aMessage) {
+ const watchOptToOption = {
+ H: "watchMasksEnabled",
+ A: "watchAwayEnabled",
+ };
+
+ // For each option, mark it as supported.
+ aMessage.isupport.value.split("").forEach(function (aWatchOpt) {
+ if (watchOptToOption.hasOwnProperty(aWatchOpt)) {
+ this[watchOptToOption[aWatchOpt]] = true;
+ }
+ }, this);
+
+ return true;
+ },
+ },
+};
+
+export var ircWATCH = {
+ name: "WATCH",
+ // Slightly above default IRC priority.
+ priority: ircHandlerPriorities.DEFAULT_PRIORITY + 10,
+ // Use WATCH if it is supported.
+ isEnabled() {
+ return !!this.watchEnabled;
+ },
+
+ commands: {
+ 251(aMessage) {
+ // RPL_LUSERCLIENT
+ // ":There are <integer> users and <integer> services on <integer> servers"
+ // Assume that this will always be sent after the 005 handler on
+ // connection registration. If WATCH is enabled, then set the new function
+ // to keep track of nicks and send the messages to watch the nicks.
+
+ // Ensure that any new buddies are set to be watched, and removed buddies
+ // are no longer watched.
+ this.trackBuddy = trackBuddyWatch;
+ this.untrackBuddy = untrackBuddyWatch;
+
+ // Build the watchlist from the current list of nicks.
+ this.trackBuddy(this.trackQueue);
+
+ // Fall through to other handlers since we're only using this as an entry
+ // point and not actually handling the message.
+ return false;
+ },
+
+ 301(aMessage) {
+ // RPL_AWAY
+ // <nick> :<away message>
+ // Set the received away message.
+ let buddy = this.buddies.get(aMessage.params[1]);
+ if (buddy) {
+ buddy.setStatus(Ci.imIStatusInfo.STATUS_AWAY, aMessage.params[2]);
+ }
+
+ // Fall through to the other implementations after setting the status
+ // message.
+ return false;
+ },
+
+ 303(aMessage) {
+ // RPL_ISON
+ // :*1<nick> *( " " <nick> )
+ // We don't want ircBase to interfere with us, so override the ISON
+ // handler to do nothing.
+ return true;
+ },
+
+ 512(aMessage) {
+ // ERR_TOOMANYWATCH
+ // Maximum size for WATCH-list is <watchlimit> entries
+ this.ERROR(
+ "Maximum size for WATCH list exceeded (" + this.watchLength + ")."
+ );
+ return true;
+ },
+
+ 597(aMessage) {
+ // RPL_REAWAY
+ // <nickname> <username> <hostname> <awaysince> :<away reason>
+ return setStatus(this, aMessage.params[1], "AWAY");
+ },
+
+ 598(aMessage) {
+ // RPL_GONEAWAY
+ // <nickname> <username> <hostname> <awaysince> :<away reason>
+ // We use a negative index as inspircd versions < 2.0.18 don't send
+ // the user's nick as the first parameter (see bug 1078223).
+ return setStatus(
+ this,
+ aMessage.params[aMessage.params.length - 5],
+ "AWAY"
+ );
+ },
+
+ 599(aMessage) {
+ // RPL_NOTAWAY
+ // <nickname> <username> <hostname> <awaysince> :is no longer away
+ // We use a negative index as inspircd versions < 2.0.18 don't send
+ // the user's nick as the first parameter (see bug 1078223).
+ return setStatus(
+ this,
+ aMessage.params[aMessage.params.length - 5],
+ "AVAILABLE"
+ );
+ },
+
+ 600(aMessage) {
+ // RPL_LOGON
+ // <nickname> <username> <hostname> <signontime> :logged on
+ return setStatus(this, aMessage.params[1], "AVAILABLE");
+ },
+
+ 601(aMessage) {
+ // RPL_LOGOFF
+ // <nickname> <username> <hostname> <lastnickchange> :logged off
+ return setStatus(this, aMessage.params[1], "OFFLINE");
+ },
+
+ 602(aMessage) {
+ // RPL_WATCHOFF
+ // <nickname> <username> <hostname> <lastnickchange> :stopped watching
+ return true;
+ },
+
+ 603(aMessage) {
+ // RPL_WATCHSTAT
+ // You have <entrycount> and are on <onlistcount> WATCH entries
+ // TODO I don't think we really need to care about this.
+ return false;
+ },
+
+ 604(aMessage) {
+ // RPL_NOWON
+ // <nickname> <username> <hostname> <lastnickchange> :is online
+ return setStatus(this, aMessage.params[1], "AVAILABLE");
+ },
+
+ 605(aMessage) {
+ // RPL_NOWOFF
+ // <nickname> <username> <hostname> <lastnickchange> :is offline
+ return setStatus(this, aMessage.params[1], "OFFLINE");
+ },
+
+ 606(aMessage) {
+ // RPL_WATCHLIST
+ // <entrylist>
+ // TODO
+ return false;
+ },
+
+ 607(aMessage) {
+ // RPL_ENDOFWATCHLIST
+ // End of WATCH <parameter>
+ // TODO
+ return false;
+ },
+
+ 608(aMessage) {
+ // RPL_CLEARWATCH
+ // Your WATCH list is now empty
+ // Note that this is optional for servers to send, so ignore it.
+ return true;
+ },
+
+ 609(aMessage) {
+ // RPL_NOWISAWAY
+ // <nickname> <username> <hostname> <awaysince> :<away reason>
+ return setStatus(this, aMessage.params[1], "AWAY");
+ },
+ },
+};
+
+export var isupportMONITOR = {
+ name: "MONITOR",
+ // Slightly above default ISUPPORT priority.
+ priority: ircHandlerPriorities.DEFAULT_PRIORITY + 10,
+ isEnabled: () => true,
+
+ commands: {
+ MONITOR(aMessage) {
+ if (!aMessage.isupport.useDefault) {
+ this.maxMonitorLength = Infinity;
+ } else {
+ let size = parseInt(aMessage.isupport.value, 10);
+ if (isNaN(size)) {
+ return false;
+ }
+ this.maxMonitorLength = size;
+ }
+
+ this.monitorEnabled = true;
+
+ // Clear our monitor list in case there is garbage in it.
+ this.sendMessage("MONITOR", "C");
+ this.monitorLength = 0;
+
+ // Kill the ISON polling loop.
+ clearTimeout(this._isOnTimer);
+
+ return true;
+ },
+ },
+};
+
+function trackBuddyMonitor(aNicks) {
+ // aNicks is an array when MONITOR is initialized, and a single nick
+ // in all later calls.
+ if (!Array.isArray(aNicks)) {
+ // We update the trackQueue if an individual nick is being added,
+ // so the nick will also be monitored after a reconnect.
+ Object.getPrototypeOf(this).trackBuddy.call(this, aNicks);
+ aNicks = [aNicks];
+ }
+
+ let nicks = aNicks;
+ if (!nicks.length) {
+ return;
+ }
+
+ let newMonitorLength = this.monitorLength + nicks.length;
+ if (newMonitorLength > this.maxMonitorLength) {
+ this.WARN(
+ "Attempting to MONITOR " +
+ newMonitorLength +
+ " nicks; maximum size is " +
+ this.maxMonitorLength +
+ "."
+ );
+ // TODO We should trim the list and add the extra users to an ISON queue,
+ // but that's not currently implemented, so just hope the server doesn't
+ // enforce it's own limit.
+ }
+ this.monitorLength = newMonitorLength;
+
+ let params = [];
+ let maxLength =
+ this.maxMessageLength -
+ 2 -
+ this.countBytes(this.buildMessage("MONITOR", "+"));
+ for (let nick of nicks) {
+ if (this.countBytes(params + " " + nick) >= maxLength) {
+ // If the message would be too long, first send this message.
+ this.sendMessage("MONITOR", ["+", params.join(",")]);
+ // Reset for the next message.
+ params = [];
+ }
+ params.push(nick);
+ }
+ this.sendMessage("MONITOR", ["+", params.join(",")]);
+}
+function untrackBuddyMonitor(aNick) {
+ --this.monitorLength;
+ this.sendMessage("MONITOR", ["-", aNick]);
+ Object.getPrototypeOf(this).untrackBuddy.call(this, aNick);
+}
+
+export var ircMONITOR = {
+ name: "MONITOR",
+ // Slightly above default IRC priority.
+ priority: ircHandlerPriorities.DEFAULT_PRIORITY + 10,
+ // Use MONITOR only if MONITOR is enabled and WATCH is not enabled, as WATCH
+ // supports more features.
+ isEnabled() {
+ return this.monitorEnabled && !this.watchEnabled;
+ },
+
+ commands: {
+ 251(aMessage) {
+ // RPL_LUSERCLIENT
+ // ":There are <integer> users and <integer> services on <integer> servers"
+ // Assume that this will always be sent after the 005 handler on
+ // connection registration. If MONITOR is enabled, then set the new
+ // function to keep track of nicks and send the messages to watch the
+ // nicks.
+
+ // Ensure that any new buddies are set to be watched, and removed buddies
+ // are no longer watched.
+ this.trackBuddy = trackBuddyMonitor;
+ this.untrackBuddy = untrackBuddyMonitor;
+
+ // Build the watchlist from the current list of nicks.
+ this.trackBuddy(this.trackQueue);
+
+ // Fall through to other handlers since we're only using this as an entry
+ // point and not actually handling the message.
+ return false;
+ },
+
+ 303(aMessage) {
+ // RPL_ISON
+ // :*1<nick> *( " " <nick> )
+ // We don't want ircBase to interfere with us, so override the ISON
+ // handler to do nothing if we're using MONITOR.
+ return true;
+ },
+
+ 730(aMessage) {
+ // RPL_MONONLINE
+ // :<server> 730 <nick> :nick!user@host[,nick!user@host]*
+ // Mark each nick as online.
+ return aMessage.params[1]
+ .split(",")
+ .map(aNick => setStatus(this, aNick.split("!", 1)[0], "AVAILABLE"))
+ .every(aResult => aResult);
+ },
+
+ 731(aMessage) {
+ // RPL_MONOFFLINE
+ // :<server> 731 <nick> :nick[,nick1]*
+ return aMessage.params[1]
+ .split(",")
+ .map(aNick => setStatus(this, aNick, "OFFLINE"))
+ .every(aResult => aResult);
+ },
+
+ 732(aMessage) {
+ // RPL_MONLIST
+ // :<server> 732 <nick> :nick[,nick1]*
+ return false;
+ },
+
+ 733(aMessage) {
+ // RPL_ENDOFMONLIST
+ // :<server> 733 <nick> :End of MONITOR list
+ return false;
+ },
+
+ 734(aMessage) {
+ // ERR_MONLISTFULL
+ // :<server> 734 <nick> <limit> <nicks> :Monitor list is full.
+ this.ERROR(
+ "Maximum size for MONITOR list exceeded (" + this.params[1] + ")."
+ );
+ return true;
+ },
+ },
+};