# HG changeset patch # User Ascrod <32915892+Ascrod@users.noreply.github.com> # Date 1560129008 14400 # Parent fa291f4d6430208f94cfa8565b8188a0432e56f7 Bug 1724589 - IRCv3: Add support for batch. r=IanN a=IanN Add handlers for netjoin, netsplit, chathistory batch types. Clean up open batches on disconnect. Ensure batches display on correct view. Batch: Make handling more flexible. diff --git a/suite/extensions/irc/js/lib/irc.js b/suite/extensions/irc/js/lib/irc.js --- a/suite/extensions/irc/js/lib/irc.js +++ b/suite/extensions/irc/js/lib/irc.js @@ -10,17 +10,17 @@ const JSIRC_ERR_CANCELLED = "JSIRCE:C"; const JSIRC_ERR_NO_SECURE = "JSIRCE:NO_SECURE"; const JSIRC_ERR_OFFLINE = "JSIRCE:OFFLINE"; const JSIRC_ERR_PAC_LOADING = "JSIRCE:PAC_LOADING"; const JSIRCV3_SUPPORTED_CAPS = [ "account-notify", "account-tag", "away-notify", - //"batch", + "batch", "cap-notify", "chghost", "echo-message", "extended-join", "invite-notify", //"labeled-response", "message-tags", //"metadata", @@ -1297,16 +1297,18 @@ function serv_disconnect(e) } if (this.isStartTLS) { this.isSecure = false; delete this.isStartTLS; } + delete this.batches; + this.connection = null; this.isConnected = false; delete this.quitting; } CIRCServer.prototype.onSendData = function serv_onsenddata (e) @@ -1562,16 +1564,27 @@ function serv_onRawData(e) e.decodeParam = decodeParam; e.code = e.params[0].toUpperCase(); // Ignore all private (inc. channel) messages, notices and invites here. if (e.ignored && ((e.code == "PRIVMSG") || (e.code == "NOTICE") || (e.code == "INVITE") || (e.code == "TAGMSG"))) return true; + // If the message is part of a batch, store it for later. + if (this.batches && e.tags["batch"] && e.code != "BATCH") + { + var reftag = e.tags["batch"]; + // Check if the batch is already open. + // If not, ignore the incoming message. + if (this.batches[reftag]) + this.batches[reftag].messages.push(e); + return false; + } + e.type = "parseddata"; e.destObject = this; e.destMethod = "onParsedData"; return true; } /* @@ -2474,16 +2487,145 @@ function my_cap (e) { dd("Unknown CAP reply " + e.params[2]); } e.destObject = this.parent; e.set = "network"; } +/* BATCH start or end */ +CIRCServer.prototype.onBatch = +function serv_batch(e) +{ + // We should at least get a ref tag. + if (e.params.length < 2) + return false; + + e.reftag = e.params[1].substring(1); + switch (e.params[1][0]) + { + case "+": + e.starting = true; + break; + case "-": + e.starting = false; + break; + default: + // Invalid reference tag. + return false; + } + var isPlayback = (this.batches && this.batches[e.reftag] && + this.batches[e.reftag].playback); + + if (!isPlayback) + { + if (e.starting) + { + // We're starting a batch, so we also need a type. + if (e.params.length < 3) + return false; + + if (!this.batches) + this.batches = new Object(); + // The batch object holds the messages queued up as part + // of this batch, and a boolean value indicating whether + // it is being played back. + var newBatch = new Object(); + newBatch.messages = [e]; + newBatch.type = e.params[2].toUpperCase(); + if (e.params[3] && (e.params[3] in this.channels)) + { + newBatch.destObject = this.channels[e.params[3]]; + } + else if (e.params[3] && (e.params[3] in this.users)) + { + newBatch.destObject = this.users[e.params[3]]; + } + else + { + newBatch.destObject = this.parent; + } + newBatch.playback = false; + this.batches[e.reftag] = newBatch; + } + else + { + if (!this.batches[e.reftag]) + { + // Got a close tag without an open tag, so ignore it. + return false; + } + + var batch = this.batches[e.reftag]; + + // Closing the batch, prepare for playback. + batch.messages.push(e); + batch.playback = true; + if (e.tags["batch"]) + { + // We are an inner batch. Append the message queue + // to the outer batch's message queue. + var parentRef = e.tags["batch"]; + var parentMsgs = this.batches[parentRef].messages; + parentMsgs = parentMsgs.concat(batch.messages); + } + else + { + // We are an outer batch. Playback! + for (var i = 0; i < batch.messages.length; i++) + { + var ev = batch.messages[i]; + ev.type = "parseddata"; + ev.destObject = this; + ev.destMethod = "onParsedData"; + this.parent.eventPump.routeEvent(ev); + } + } + } + return false; + } + else + { + // Batch command is ready for handling. + e.batchtype = this.batches[e.reftag].type; + e.destObject = this.batches[e.reftag].destObject; + if (e.destObject.TYPE == "CIRCChannel") + { + e.set = "channel"; + } + else + { + e.set = "network"; + } + + if (!e.starting) + { + // If we've reached the end of a batch in playback, + // do some cleanup. + delete this.batches[e.reftag]; + if (Object.entries(this.batches).length == 0) + delete this.batches; + } + + // Massage the batchtype into a method name for handlers: + // netsplit - onNetsplitBatch + // some-batch-type - onSomeBatchTypeBatch + // example.com/example - onExampleComExampleBatch + var batchCode = e.batchtype.split(/[\.\/-]/).map(function(s) + { + return s[0].toUpperCase() + s.substr(1).toLowerCase(); + }).join(""); + e.destMethod = "on" + batchCode + "Batch"; + + if (!e.destObject[e.destMethod]) + e.destMethod = "onUnknownBatch"; + } +} + /* SASL authentication responses */ CIRCServer.prototype.on902 = /* Nick locked */ CIRCServer.prototype.on903 = /* Auth success */ CIRCServer.prototype.on904 = /* Auth failed */ CIRCServer.prototype.on905 = /* Command too long */ CIRCServer.prototype.on906 = /* Aborted */ CIRCServer.prototype.on907 = /* Already authenticated */ CIRCServer.prototype.on908 = /* Mechanisms */ diff --git a/suite/extensions/irc/locales/en-US/chrome/chatzilla.properties b/suite/extensions/irc/locales/en-US/chrome/chatzilla.properties --- a/suite/extensions/irc/locales/en-US/chrome/chatzilla.properties +++ b/suite/extensions/irc/locales/en-US/chrome/chatzilla.properties @@ -937,16 +937,25 @@ msg.jumpto.err.noanchor = The anchor c msg.banlist.item = "%S banned %S from %S on %S. msg.banlist.button = [[Remove][Remove this ban][%S]] msg.banlist.end = End of %S ban list. msg.exceptlist.item = "%S excepted %S from bans in %S on %S. msg.exceptlist.button = [[Remove][Remove this ban exception][%S]] msg.exceptlist.end = End of %S exception list. +msg.batch.netsplit.start = Netsplit (%S %S) +msg.batch.netsplit.end = End of netsplit. +msg.batch.netjoin.start = Net reconnect (%S %S) +msg.batch.netjoin.end = End of net reconnect. +msg.batch.chathistory.start = Chat history for %S +msg.batch.chathistory.end = End of chat history. +msg.batch.unknown.start = Batch %S (%S) +msg.batch.unknown.end = End of batch. + msg.channel.needops = You need to be an operator in %S to do that. msg.ctcphelp.clientinfo = CLIENTINFO gives information on available CTCP commands msg.ctcphelp.action = ACTION performs an action at the user msg.ctcphelp.time = TIME gives the local date and time for the client msg.ctcphelp.version = VERSION returns the client's version msg.ctcphelp.source = SOURCE returns an address where you can obtain the client msg.ctcphelp.os = OS returns the client's host's operating system and version diff --git a/suite/extensions/irc/xul/content/handlers.js b/suite/extensions/irc/xul/content/handlers.js --- a/suite/extensions/irc/xul/content/handlers.js +++ b/suite/extensions/irc/xul/content/handlers.js @@ -2628,16 +2628,91 @@ function my_auth(e) e.server.sendAuthAbort(); return; } var auth = username + '\0' + username + '\0' + password; e.server.sendAuthResponse(auth); } +CIRCNetwork.prototype.onNetsplitBatch = +function my_netsplit_batch(e) +{ + for (var c in this.primServ.channels) + { + if (e.starting) + { + this.startMsgGroup(e.reftag, getMsg(MSG_BATCH_NETSPLIT_START, + [e.params[3], + e.params[4]]), + e.batchtype); + } + else + { + this.display(MSG_BATCH_NETSPLIT_END, e.batchtype); + this.endMsgGroup(); + } + } +} + +CIRCNetwork.prototype.onNetjoinBatch = +function my_netjoin_batch(e) +{ + for (var c in this.primServ.channels) + { + if (e.starting) + { + this.startMsgGroup(e.reftag, getMsg(MSG_BATCH_NETJOIN_START, + [e.params[3], + e.params[4]]), + e.batchtype); + } + else + { + this.display(MSG_BATCH_NETJOIN_END, e.batchtype); + this.endMsgGroup(); + } + } +} + +CIRCChannel.prototype.onChathistoryBatch = +function my_chathistory_batch(e) +{ + if (e.starting) + { + this.startMsgGroup(e.reftag, getMsg(MSG_BATCH_CHATHISTORY_START, + [e.params[3]]), + e.batchtype); + } + else + { + this.display(MSG_BATCH_CHATHISTORY_END, e.batchtype); + this.endMsgGroup(); + } +} + +CIRCNetwork.prototype.onUnknownBatch = +CIRCChannel.prototype.onUnknownBatch = +CIRCUser.prototype.onUnknownBatch = +function my_unknown_batch(e) +{ + if (e.starting) + { + this.startMsgGroup(e.reftag, getMsg(MSG_BATCH_UNKNOWN, + [e.batchtype, + e.params.slice(3)]), + "BATCH"); + } + else + { + this.display(MSG_BATCH_UNKNOWN_END, e.batchtype); + this.endMsgGroup(); + } +} + /* user away status */ CIRCNetwork.prototype.onAway = function my_away(e) { var userlist = document.getElementById("user-list"); for (var c in e.server.channels) { var chan = e.server.channels[c];