From 48017b680ede0dbfb121d1184dfbd13536cfc53f Mon Sep 17 00:00:00 2001 From: Janne Koschinski Date: Mon, 20 Jul 2020 19:45:42 -0400 Subject: [PATCH] core: Implement echo-message, disabled by default Implement the IRCv3 "echo-message" capability, allowing Quassel to show how the network displayed sent messages, including any added latency. As this introduces additional latency, don't request this by default. It can be manually activated with "/CAP REQ echo-message", e.g. in the list of commands to perform on connect. There may be other issues as well, such as with CTCP handling. Here be danger! This will serve as the building block for communicating when a message is pending being sent, vs. being delivered. This requires support for IRCv3 "batch" and "labeled-response", alongside protocol changes to allow updating the status of existing Message objects. Also add missing sources of setting the "::Self" flag on messages, ensuring that echoed messages will be properly handled, e.g. with CTCP events. [Original commit by justJanne, made opt-in by digitalcircuit] See https://ircv3.net/specs/extensions/echo-message-3.2.html And https://ircv3.net/specs/extensions/labeled-response [Addendum] If "echo-message" was to be made enabled by default as-is, the Features tab in the Network settings page can offer a way to opt out. Here's how it might look: Checkbox with widget name of "enableCapEchoMessage": [x] Show outgoing messages after delivery Tooltip:

Wait until the IRC network has received the message to show it in Quassel, and show any changes the network has made to the message (e.g. removing formatting).

It may take longer for outgoing messages to appear.

Toggles the IRCv3 'echo-message' capability.

"echo-message" should not be enabled by default yet, I just spent a fair bit of time thinking on how to phrase this and wanted to preserve it in the unlikely case it is useful in the future. And here's the reasoning to go with this now-theoretical commit: Reasoning: This will unavoidably add latency to showing messages you've sent, and if a network incorrectly echoes messages back, this could result in losing message history. Some may prefer the lower-latency of having Quassel locally show messages, and we're a fair distance away from implementing message labels in order to provide a "sending"/"sent" message feedback (or any form of message editing at all) - messages are assumed to be immutable once received. --- src/common/irccap.h | 8 +++ src/core/coreuserinputhandler.cpp | 90 +++++++++++++++++-------------- src/core/ctcpparser.cpp | 9 ++++ src/core/eventstringifier.cpp | 23 ++++++-- 4 files changed, 86 insertions(+), 44 deletions(-) diff --git a/src/common/irccap.h b/src/common/irccap.h index 9fb40e1f..3d1e793d 100644 --- a/src/common/irccap.h +++ b/src/common/irccap.h @@ -79,6 +79,13 @@ namespace IrcCap { */ const QString CHGHOST = "chghost"; + /** + * Server sending own messages back. + * + * https://ircv3.net/specs/extensions/echo-message-3.2.html + */ + const QString ECHO_MESSAGE = "echo-message"; + /** * Extended join information. * @@ -173,6 +180,7 @@ namespace IrcCap { AWAY_NOTIFY, CAP_NOTIFY, CHGHOST, + //ECHO_MESSAGE, // Postponed for message pending UI with batch + labeled-response EXTENDED_JOIN, INVITE_NOTIFY, MESSAGE_TAGS, diff --git a/src/core/coreuserinputhandler.cpp b/src/core/coreuserinputhandler.cpp index 8ab8dce9..b64eb73c 100644 --- a/src/core/coreuserinputhandler.cpp +++ b/src/core/coreuserinputhandler.cpp @@ -183,14 +183,16 @@ void CoreUserInputHandler::handleCtcp(const BufferInfo& bufferInfo, const QStrin // FIXME make this a proper event coreNetwork()->coreSession()->ctcpParser()->query(coreNetwork(), nick, ctcpTag, message); - emit displayMsg(NetworkInternalMessage( - Message::Action, - BufferInfo::StatusBuffer, - "", - verboseMessage, - network()->myNick(), - Message::Flag::Self - )); + if (!network()->capEnabled(IrcCap::ECHO_MESSAGE)) { + emit displayMsg(NetworkInternalMessage( + Message::Action, + BufferInfo::StatusBuffer, + "", + verboseMessage, + network()->myNick(), + Message::Flag::Self + )); + } } void CoreUserInputHandler::handleDelkey(const BufferInfo& bufferInfo, const QString& msg) @@ -510,14 +512,16 @@ void CoreUserInputHandler::handleMe(const BufferInfo& bufferInfo, const QString& for (const auto& message : messages) { // Handle each separated message independently coreNetwork()->coreSession()->ctcpParser()->query(coreNetwork(), bufferInfo.bufferName(), "ACTION", message); - emit displayMsg(NetworkInternalMessage( - Message::Action, - bufferInfo.type(), - bufferInfo.bufferName(), - message, - network()->myNick(), - Message::Self - )); + if (!network()->capEnabled(IrcCap::ECHO_MESSAGE)) { + emit displayMsg(NetworkInternalMessage( + Message::Action, + bufferInfo.type(), + bufferInfo.bufferName(), + message, + network()->myNick(), + Message::Self + )); + } } } @@ -590,14 +594,16 @@ void CoreUserInputHandler::handleNotice(const BufferInfo& bufferInfo, const QStr params.clear(); params << serverEncode(bufferName) << channelEncode(bufferInfo.bufferName(), message); emit putCmd("NOTICE", params); - emit displayMsg(NetworkInternalMessage( - Message::Notice, - typeByTarget(bufferName), - bufferName, - message, - network()->myNick(), - Message::Self - )); + if (!network()->capEnabled(IrcCap::ECHO_MESSAGE)) { + emit displayMsg(NetworkInternalMessage( + Message::Notice, + typeByTarget(bufferName), + bufferName, + message, + network()->myNick(), + Message::Self + )); + } } } @@ -680,14 +686,16 @@ void CoreUserInputHandler::handleQuery(const BufferInfo& bufferInfo, const QStri // handleMsg is a no-op if message is empty } else { - emit displayMsg(NetworkInternalMessage( - Message::Plain, - BufferInfo::QueryBuffer, - target, - message, - network()->myNick(), - Message::Self - )); + if (!network()->capEnabled(IrcCap::ECHO_MESSAGE)) { + emit displayMsg(NetworkInternalMessage( + Message::Plain, + BufferInfo::QueryBuffer, + target, + message, + network()->myNick(), + Message::Self + )); + } // handleMsg needs the target specified at the beginning of the message handleMsg(bufferInfo, target + " " + message); } @@ -731,14 +739,16 @@ void CoreUserInputHandler::handleSay(const BufferInfo& bufferInfo, const QString #else putPrivmsg(bufferInfo.bufferName(), message, encodeFunc); #endif - emit displayMsg(NetworkInternalMessage( - Message::Plain, - bufferInfo.type(), - bufferInfo.bufferName(), - message, - network()->myNick(), - Message::Self - )); + if (!network()->capEnabled(IrcCap::ECHO_MESSAGE)) { + emit displayMsg(NetworkInternalMessage( + Message::Plain, + bufferInfo.type(), + bufferInfo.bufferName(), + message, + network()->myNick(), + Message::Self + )); + } } } diff --git a/src/core/ctcpparser.cpp b/src/core/ctcpparser.cpp index b35f12bf..9b93b844 100644 --- a/src/core/ctcpparser.cpp +++ b/src/core/ctcpparser.cpp @@ -66,6 +66,9 @@ void CtcpParser::displayMsg(NetworkEvent* event, return; MessageEvent* msgEvent = new MessageEvent(msgType, event->network(), std::move(msg), std::move(sender), std::move(target), msgFlags, event->timestamp()); + if (event->testFlag(EventManager::Self)) { + msgEvent->setFlag(EventManager::Self); + } emit newEvent(msgEvent); } @@ -236,6 +239,9 @@ void CtcpParser::parseSimple(IrcEventRawMessage* e, ctcpparam, e->timestamp(), uuid); + if (e->testFlag(EventManager::Self)) { + event->setFlag(EventManager::Self); + } emit newEvent(event); CtcpEvent* flushEvent = new CtcpEvent(EventManager::CtcpEventFlush, e->network(), @@ -313,6 +319,9 @@ void CtcpParser::parseStandard(IrcEventRawMessage* e, ctcpparam, e->timestamp(), uuid); + if (e->testFlag(EventManager::Self)) { + event->setFlag(EventManager::Self); + } ctcpEvents << event; } } diff --git a/src/core/eventstringifier.cpp b/src/core/eventstringifier.cpp index 740310da..a852be1b 100644 --- a/src/core/eventstringifier.cpp +++ b/src/core/eventstringifier.cpp @@ -801,7 +801,12 @@ void EventStringifier::processCtcpEvent(CtcpEvent* e) if (e->type() != EventManager::CtcpEvent) return; - if (e->testFlag(EventManager::Self)) { + // Only stringify outgoing CTCP messages this way + if (e->testFlag(EventManager::Self) && + // Only stringify CTCP queries this way + e->ctcpType() == CtcpEvent::CtcpType::Query && + // Always handle ACTIONs as if they were sent from network, to properly handle echo-message + e->ctcpCmd() != "ACTION") { displayMsg(e, Message::Action, tr("sending CTCP-%1 request to %2").arg(e->ctcpCmd(), e->target()), @@ -823,14 +828,24 @@ void EventStringifier::defaultHandler(const QString& ctcpCmd, CtcpEvent* e) //: Optional "unknown" in "Received unknown CTCP-FOO request by bar" unknown = tr("unknown") + ' '; displayMsg(e, Message::Server, tr("Received %1CTCP-%2 request by %3").arg(unknown, e->ctcpCmd(), e->prefix())); - return; + } else { + // Ignore echo messages for our own answers + if (!e->testFlag(EventManager::Self)) { + displayMsg(e, Message::Server, + tr("Received CTCP-%1 answer from %2: %3").arg(e->ctcpCmd(), nickFromMask(e->prefix()),e->param())); + } } - displayMsg(e, Message::Server, tr("Received CTCP-%1 answer from %2: %3").arg(e->ctcpCmd(), nickFromMask(e->prefix()), e->param())); } void EventStringifier::handleCtcpAction(CtcpEvent* e) { - displayMsg(e, Message::Action, e->param(), e->prefix(), e->target()); + Message::Flag msgFlags = Message::Flag::None; + if (e->testFlag(EventManager::Self)) { + // Mark the message as Self + msgFlags = Message::Self; + } + + displayMsg(e, Message::Action, e->param(), e->prefix(), e->target(), msgFlags); } void EventStringifier::handleCtcpPing(CtcpEvent* e) -- 2.20.1