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:
<p>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).</p>
<p>It may take longer for outgoing messages to appear.</p>
<p><i>Toggles the IRCv3 'echo-message' capability.<i></p>
"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.
*/
const QString CHGHOST = "chghost";
*/
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.
*
/**
* Extended join information.
*
AWAY_NOTIFY,
CAP_NOTIFY,
CHGHOST,
AWAY_NOTIFY,
CAP_NOTIFY,
CHGHOST,
+ //ECHO_MESSAGE, // Postponed for message pending UI with batch + labeled-response
EXTENDED_JOIN,
INVITE_NOTIFY,
MESSAGE_TAGS,
EXTENDED_JOIN,
INVITE_NOTIFY,
MESSAGE_TAGS,
// FIXME make this a proper event
coreNetwork()->coreSession()->ctcpParser()->query(coreNetwork(), nick, ctcpTag, message);
// 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)
}
void CoreUserInputHandler::handleDelkey(const BufferInfo& bufferInfo, const QString& msg)
for (const auto& message : messages) {
// Handle each separated message independently
coreNetwork()->coreSession()->ctcpParser()->query(coreNetwork(), bufferInfo.bufferName(), "ACTION", message);
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
+ ));
+ }
params.clear();
params << serverEncode(bufferName) << channelEncode(bufferInfo.bufferName(), message);
emit putCmd("NOTICE", params);
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
+ ));
+ }
// handleMsg is a no-op if message is empty
}
else {
// 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);
}
// handleMsg needs the target specified at the beginning of the message
handleMsg(bufferInfo, target + " " + message);
}
#else
putPrivmsg(bufferInfo.bufferName(), message, encodeFunc);
#endif
#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
+ ));
+ }
return;
MessageEvent* msgEvent = new MessageEvent(msgType, event->network(), std::move(msg), std::move(sender), std::move(target), msgFlags, event->timestamp());
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);
}
emit newEvent(msgEvent);
}
ctcpparam,
e->timestamp(),
uuid);
ctcpparam,
e->timestamp(),
uuid);
+ if (e->testFlag(EventManager::Self)) {
+ event->setFlag(EventManager::Self);
+ }
emit newEvent(event);
CtcpEvent* flushEvent = new CtcpEvent(EventManager::CtcpEventFlush,
e->network(),
emit newEvent(event);
CtcpEvent* flushEvent = new CtcpEvent(EventManager::CtcpEventFlush,
e->network(),
ctcpparam,
e->timestamp(),
uuid);
ctcpparam,
e->timestamp(),
uuid);
+ if (e->testFlag(EventManager::Self)) {
+ event->setFlag(EventManager::Self);
+ }
if (e->type() != EventManager::CtcpEvent)
return;
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()),
displayMsg(e,
Message::Action,
tr("sending CTCP-%1 request to %2").arg(e->ctcpCmd(), e->target()),
//: 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()));
//: 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()));
+ } 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)
{
}
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)
}
void EventStringifier::handleCtcpPing(CtcpEvent* e)