From d778861aa194578cd90e019b64fe285ea58746c4 Mon Sep 17 00:00:00 2001 From: Shane Synan Date: Mon, 20 Jul 2020 21:56:39 -0400 Subject: [PATCH] core: Implement account-tag Implement the "account-tag" capability, updating IrcUser account status with information from the "account" tag when available. According to the specification, "account" will be sent with all user-initiated commands when logged in, and will NOT be sent when logged out. Quassel takes a conservative approach combining the approaches taken by other clients. For PRIVMSG and NOTICE, "account" tag is used to set whether the IrcUser is logged in if present, AND logged out if NOT present. For all other messages, if the "account" tag is present and the prefix refers to an EXISTING IrcUser, the IrcUser will be set as logged in, but will NOT be set as logged out if the tag is missing. Server/client details as of 2020-7-20 * Oragono (server) Sends account tag with any client-initiated command https://github.com/oragono/oragono/blob/b5855e61941e4dc33b64f6d61996a23686136166/irc/client.go#L1474 * InspIRCd (server) Sends account tag with any client-initiated command https://github.com/inspircd/inspircd/blob/4a6fedd9324d87349a806c9c1d0ae6e7d3c1fd38/src/modules/m_ircv3_accounttag.cpp * BitBot (client/bot) Marks as logged in, does not handle logging out https://github.com/jesopo/bitbot/blob/9dfbe3723c8af97c9fbd35a3391298bf6f641043/src/core_modules/permissions/__init__.py#L117-L121 * PIRC.pl web client (client) Marks as logged in, does not handle logging out https://github.com/pirc-pl/pirc-gateway/blob/63d7be177568e99cada61cb53f1176b857bfca72/js/gateway_def.js#L2605-L2607 * KiwiIRC (client) Marks as logged in AND logged out via PRIVMSG, NOTICE, WALLOPS, ignores other commands https://github.com/kiwiirc/irc-framework/blob/11e9e8141dbbff68d730d2d840454949d2b2b5f5/src/commands/handlers/messaging.js#L9-L139 See https://ircv3.net/specs/extensions/account-tag-3.2 --- src/common/irccap.h | 8 +++++ src/common/irctags.h | 7 +++++ src/core/ircparser.cpp | 66 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/common/irccap.h b/src/common/irccap.h index 132bd71a..0bcb66d2 100644 --- a/src/common/irccap.h +++ b/src/common/irccap.h @@ -48,6 +48,13 @@ namespace IrcCap { */ const uint ACCOUNT_NOTIFY_WHOX_NUM = 369; + /** + * Send account information as a tag with all commands sent by a user. + * + * http://ircv3.net/specs/extensions/account-notify-3.1.html + */ + const QString ACCOUNT_TAG = "account-tag"; + /** * Away change notification. * @@ -155,6 +162,7 @@ namespace IrcCap { * List of capabilities currently implemented and requested during capability negotiation. */ const QStringList knownCaps = QStringList{ACCOUNT_NOTIFY, + ACCOUNT_TAG, AWAY_NOTIFY, CAP_NOTIFY, CHGHOST, diff --git a/src/common/irctags.h b/src/common/irctags.h index 0687a271..ed1f5a74 100644 --- a/src/common/irctags.h +++ b/src/common/irctags.h @@ -28,6 +28,13 @@ */ namespace IrcTags { + /** + * Services account status with user messages + * + * https://ircv3.net/specs/extensions/account-tag-3.2 + */ + const IrcTagKey ACCOUNT = IrcTagKey{"", "account", false}; + /** * Server time for messages. * diff --git a/src/core/ircparser.cpp b/src/core/ircparser.cpp index ccd39505..1a2a1fdb 100644 --- a/src/core/ircparser.cpp +++ b/src/core/ircparser.cpp @@ -129,6 +129,26 @@ void IrcParser::processNetworkIncoming(NetworkDataEvent* e) } } + if (net->capEnabled(IrcCap::ACCOUNT_TAG) && tags.contains(IrcTags::ACCOUNT)) { + // Whenever account-tag is specified, update the relevant IrcUser if it exists + // Logged-out status is handled in specific commands (PRIVMSG, NOTICE, etc) + // + // Don't use "updateNickFromMask" here to ensure this only updates existing IrcUsers and + // won't create a new IrcUser. This guards against an IRC server setting "account" tag in + // nonsensical places, e.g. for messages that are not user sent. + IrcUser* ircuser = net->ircUser(prefix); + if (ircuser) { + ircuser->setAccount(tags[IrcTags::ACCOUNT]); + } + + // NOTE: if "account-tag" is enabled and no "account" tag is sent, the given user isn't + // logged in ONLY IF it is a user-initiated command. Quassel follows a mixture of what + // other clients do here - only marking as logged out via PRIVMSG/NOTICE, but marking logged + // in via any message. + // + // See https://ircv3.net/specs/extensions/account-tag-3.2 + } + QList events; EventManager::EventType type = EventManager::Invalid; @@ -169,7 +189,24 @@ void IrcParser::processNetworkIncoming(NetworkDataEvent* e) if (checkParamCount(cmd, params, 1)) { QString senderNick = nickFromMask(prefix); - net->updateNickFromMask(prefix); + // Fetch/create the relevant IrcUser, and store it for later updates + IrcUser* ircuser = net->updateNickFromMask(prefix); + + // Handle account-tag + if (ircuser && net->capEnabled(IrcCap::ACCOUNT_TAG)) { + if (tags.contains(IrcTags::ACCOUNT)) { + // Account tag available, set account. + // This duplicates the generic account-tag handling in case a new IrcUser object + // was just created. + ircuser->setAccount(tags[IrcTags::ACCOUNT]); + } + else { + // PRIVMSG is user sent; it's safe to assume the user has logged out. + // "*" is used to represent logged-out. + ircuser->setAccount("*"); + } + } + // Check if the sender is our own nick. If so, treat message as if sent by ourself. // See http://ircv3.net/specs/extensions/echo-message-3.2.html // Cache the result to avoid multiple redundant comparisons @@ -212,6 +249,9 @@ void IrcParser::processNetworkIncoming(NetworkDataEvent* e) // Cache the result to avoid multiple redundant comparisons bool isSelfMessage = net->isMyNick(nickFromMask(prefix)); + // Only update from the prefix once during the loop + bool updatedFromPrefix = false; + QStringList targets = net->serverDecode(params.at(0)).split(',', QString::SkipEmptyParts); QStringList::const_iterator targetIter; for (targetIter = targets.constBegin(); targetIter != targets.constEnd(); ++targetIter) { @@ -248,7 +288,29 @@ void IrcParser::processNetworkIncoming(NetworkDataEvent* e) if (!isSelfMessage) { target = nickFromMask(prefix); } - net->updateNickFromMask(prefix); + + if (!updatedFromPrefix) { + // Don't repeat this within the loop, the prefix doesn't change + updatedFromPrefix = true; + + // Fetch/create the relevant IrcUser, and store it for later updates + IrcUser* ircuser = net->updateNickFromMask(prefix); + + // Handle account-tag + if (ircuser && net->capEnabled(IrcCap::ACCOUNT_TAG)) { + if (tags.contains(IrcTags::ACCOUNT)) { + // Account tag available, set account. + // This duplicates the generic account-tag handling in case a + // new IrcUser object was just created. + ircuser->setAccount(tags[IrcTags::ACCOUNT]); + } + else { + // NOTICE is user sent; it's safe to assume the user has + // logged out. "*" is used to represent logged-out. + ircuser->setAccount("*"); + } + } + } } } -- 2.20.1