+void CoreSessionEventProcessor::processIrcEvent317(IrcEvent *e)
+{
+ if (!checkParamCount(e, 2))
+ return;
+
+ QDateTime loginTime;
+
+ int idleSecs = e->params()[1].toInt();
+ if (e->params().count() > 3) { // if we have more then 3 params we have the above mentioned "real life" situation
+ // Allow for 64-bit time
+ qint64 logintime = e->params()[2].toLongLong();
+ // Time in IRC protocol is defined as seconds. Convert from seconds instead.
+ // See https://doc.qt.io/qt-5/qdatetime.html#fromSecsSinceEpoch
+#if QT_VERSION >= 0x050800
+ loginTime = QDateTime::fromSecsSinceEpoch(logintime);
+#else
+ // fromSecsSinceEpoch() was added in Qt 5.8. Manually downconvert to seconds for
+ // now.
+ // See https://doc.qt.io/qt-5/qdatetime.html#fromMSecsSinceEpoch
+ loginTime = QDateTime::fromMSecsSinceEpoch((qint64)(logintime * 1000));
+#endif
+ }
+
+ IrcUser *ircuser = e->network()->ircUser(e->params()[0]);
+ if (ircuser) {
+ ircuser->setIdleTime(e->timestamp().addSecs(-idleSecs));
+ if (loginTime.isValid())
+ ircuser->setLoginTime(loginTime);
+ }
+}
+
+
+/* RPL_LIST - "<channel> <# visible> :<topic>" */
+void CoreSessionEventProcessor::processIrcEvent322(IrcEvent *e)
+{
+ if (!checkParamCount(e, 1))
+ return;
+
+ QString channelName;
+ quint32 userCount = 0;
+ QString topic;
+
+ switch (e->params().count()) {
+ case 3:
+ topic = e->params()[2];
+ [[clang::fallthrough]];
+ case 2:
+ userCount = e->params()[1].toUInt();
+ [[clang::fallthrough]];
+ case 1:
+ channelName = e->params()[0];
+ [[clang::fallthrough]];
+ default:
+ break;
+ }
+ if (coreSession()->ircListHelper()->addChannel(e->networkId(), channelName, userCount, topic))
+ e->stop(); // consumed by IrcListHelper, so don't further process/show this event
+}
+
+
+/* RPL_LISTEND ":End of LIST" */
+void CoreSessionEventProcessor::processIrcEvent323(IrcEvent *e)
+{
+ if (!checkParamCount(e, 1))
+ return;
+
+ if (coreSession()->ircListHelper()->endOfChannelList(e->networkId()))
+ e->stop(); // consumed by IrcListHelper, so don't further process/show this event
+}
+
+
+/* RPL_CHANNELMODEIS - "<channel> <mode> <mode params>" */
+void CoreSessionEventProcessor::processIrcEvent324(IrcEvent *e)
+{
+ processIrcEventMode(e);
+}
+
+
+/* RPL_WHOISACCOUNT - "<nick> <account> :is authed as" */
+void CoreSessionEventProcessor::processIrcEvent330(IrcEvent *e)
+{
+ // Though the ":is authed as" remark should always be there, we should handle cases when it's
+ // not included, too.
+ if (!checkParamCount(e, 2))
+ return;
+
+ IrcUser *ircuser = e->network()->ircUser(e->params().at(0));
+ if (ircuser) {
+ ircuser->setAccount(e->params().at(1));
+ }
+}
+
+
+/* RPL_NOTOPIC */
+void CoreSessionEventProcessor::processIrcEvent331(IrcEvent *e)
+{
+ if (!checkParamCount(e, 1))
+ return;
+
+ IrcChannel *chan = e->network()->ircChannel(e->params()[0]);
+ if (chan)
+ chan->setTopic(QString());
+}
+
+
+/* RPL_TOPIC */
+void CoreSessionEventProcessor::processIrcEvent332(IrcEvent *e)
+{
+ if (!checkParamCount(e, 2))
+ return;
+
+ IrcChannel *chan = e->network()->ircChannel(e->params()[0]);
+ if (chan)
+ chan->setTopic(e->params()[1]);
+}
+
+
+/* RPL_WHOREPLY: "<channel> <user> <host> <server> <nick>
+ ( "H" / "G" > ["*"] [ ( "@" / "+" ) ] :<hopcount> <real name>" */
+void CoreSessionEventProcessor::processIrcEvent352(IrcEvent *e)
+{
+ if (!checkParamCount(e, 6))
+ return;
+
+ QString channel = e->params()[0];
+ // Store the nick separate from ircuser for AutoWho check below
+ QString nick = e->params()[4];
+ IrcUser *ircuser = e->network()->ircUser(nick);
+ if (ircuser) {
+ // Only process the WHO information if an IRC user exists. Don't create an IRC user here;
+ // there's no way to track when the user quits, which would leave a phantom IrcUser lying
+ // around.
+ // NOTE: Whenever MONITOR support is introduced, the IrcUser will be created by an
+ // RPL_MONONLINE numeric before any WHO commands are run.
+ processWhoInformation(e->network(), channel, ircuser, e->params()[3], e->params()[1],
+ e->params()[2], e->params()[5], e->params().last().section(" ", 1));
+ }
+
+ // Check if channel name has a who in progress.
+ // If not, then check if user nickname has a who in progress. Use nick directly; don't use
+ // ircuser as that may be deleted (e.g. nick joins channel, leaves before WHO reply received).
+ if (coreNetwork(e)->isAutoWhoInProgress(channel) ||
+ (coreNetwork(e)->isAutoWhoInProgress(nick))) {
+ e->setFlag(EventManager::Silent);
+ }
+}
+
+
+/* RPL_NAMREPLY */
+void CoreSessionEventProcessor::processIrcEvent353(IrcEvent *e)
+{
+ if (!checkParamCount(e, 3))
+ return;
+
+ // param[0] is either "=", "*" or "@" indicating a public, private or secret channel
+ // we don't use this information at the time beeing
+ QString channelname = e->params()[1];
+
+ IrcChannel *channel = e->network()->ircChannel(channelname);
+ if (!channel) {
+ qWarning() << Q_FUNC_INFO << "Received unknown target channel:" << channelname;
+ return;
+ }
+
+ QStringList nicks;
+ QStringList modes;
+
+ // Cache result of multi-prefix to avoid unneeded casts and lookups with each iteration.
+ bool _useCapMultiPrefix = coreNetwork(e)->capEnabled(IrcCap::MULTI_PREFIX);
+
+ foreach(QString nick, e->params()[2].split(' ', QString::SkipEmptyParts)) {
+ QString mode;
+
+ if (_useCapMultiPrefix) {
+ // If multi-prefix is enabled, all modes will be sent in NAMES replies.
+ // :hades.arpa 353 guest = #tethys :~&@%+aji &@Attila @+alyx +KindOne Argure
+ // See: http://ircv3.net/specs/extensions/multi-prefix-3.1.html
+ while (e->network()->prefixes().contains(nick[0])) {
+ // Mode found in 1 left-most character, add it to the list.
+ // Note: sending multiple modes may cause a warning in older clients.
+ // In testing, the clients still seemed to function fine.
+ mode.append(e->network()->prefixToMode(nick[0]));
+ // Remove this mode from the nick
+ nick = nick.remove(0, 1);
+ }
+ } else if (e->network()->prefixes().contains(nick[0])) {
+ // Multi-prefix is disabled and a mode prefix was found.
+ mode = e->network()->prefixToMode(nick[0]);
+ nick = nick.mid(1);
+ }
+
+ // If userhost-in-names capability is enabled, the following will be
+ // in the form "nick!user@host" rather than "nick". This works without
+ // special handling as the following use nickFromHost() as needed.
+ // See: http://ircv3.net/specs/extensions/userhost-in-names-3.2.html
+
+ nicks << nick;
+ modes << mode;
+ }
+
+ channel->joinIrcUsers(nicks, modes);
+}
+
+
+/* RPL_WHOSPCRPL: "<yournick> 152 #<channel> ~<ident> <host> <servname> <nick>
+ ("H"/ "G") <account> :<realname>"
+<channel> is * if not specific to any channel
+<account> is * if not logged in
+Follows HexChat's usage of 'whox'
+See https://github.com/hexchat/hexchat/blob/c874a9525c9b66f1d5ddcf6c4107d046eba7e2c5/src/common/proto-irc.c#L750
+And http://faerion.sourceforge.net/doc/irc/whox.var*/
+void CoreSessionEventProcessor::processIrcEvent354(IrcEvent *e)
+{
+ // First only check if at least one parameter exists. Otherwise, it'll stop the result from
+ // being shown if the user chooses different parameters.
+ if (!checkParamCount(e, 1))
+ return;
+
+ if (e->params()[0].toUInt() != IrcCap::ACCOUNT_NOTIFY_WHOX_NUM) {
+ // Ignore WHOX replies without expected number for we have no idea what fields are specified
+ return;
+ }