cmake: avoid de-duplication of user's CXXFLAGS
[quassel.git] / src / core / eventstringifier.cpp
index 26a2bbe..a9c68c8 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-2013 by the Quassel Project                        *
+ *   Copyright (C) 2005-2022 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
 
 #include "coresession.h"
 #include "ctcpevent.h"
+#include "irctags.h"
 #include "messageevent.h"
 
-EventStringifier::EventStringifier(CoreSession *parent) : BasicHandler("handleCtcp", parent),
-    _coreSession(parent),
-    _whois(false)
+EventStringifier::EventStringifier(CoreSession* parent)
+    : BasicHandler("handleCtcp", parent)
+    , _coreSession(parent)
+    , _whois(false)
 {
-    connect(this, SIGNAL(newMessageEvent(Event *)), coreSession()->eventManager(), SLOT(postEvent(Event *)));
+    connect(this, &EventStringifier::newMessageEvent, coreSession()->eventManager(), &EventManager::postEvent);
 }
 
-
-void EventStringifier::displayMsg(NetworkEvent *event, Message::Type msgType, const QString &msg, const QString &sender,
-    const QString &target, Message::Flags msgFlags)
+void EventStringifier::displayMsg(NetworkEvent* event,
+                                  Message::Type msgType,
+                                  QString msg,
+                                  QString sender,
+                                  QString target,
+                                  Message::Flags msgFlags)
 {
     if (event->flags().testFlag(EventManager::Silent))
         return;
 
-    MessageEvent *msgEvent = createMessageEvent(event, msgType, msg, sender, target, msgFlags);
-    //sendMessageEvent(msgEvent);
+    MessageEvent* msgEvent = createMessageEvent(event, msgType, std::move(msg), std::move(sender), std::move(target), msgFlags);
+    // sendMessageEvent(msgEvent);
     emit newMessageEvent(msgEvent);
 }
 
-
-MessageEvent *EventStringifier::createMessageEvent(NetworkEvent *event, Message::Type msgType, const QString &msg, const QString &sender,
-    const QString &target, Message::Flags msgFlags)
+MessageEvent* EventStringifier::createMessageEvent(NetworkEvent* event,
+                                                   Message::Type msgType,
+                                                   QString msg,
+                                                   QString sender,
+                                                   QString target,
+                                                   Message::Flags msgFlags)
 {
-    MessageEvent *msgEvent = new MessageEvent(msgType, event->network(), msg, sender, target, msgFlags);
-    msgEvent->setTimestamp(event->timestamp());
+    MessageEvent* msgEvent = new MessageEvent(msgType, event->network(), std::move(msg), std::move(sender), std::move(target), msgFlags, event->timestamp());
     return msgEvent;
 }
 
-
-bool EventStringifier::checkParamCount(IrcEvent *e, int minParams)
+bool EventStringifier::checkParamCount(IrcEvent* e, int minParams)
 {
     if (e->params().count() < minParams) {
         if (e->type() == EventManager::IrcEventNumeric) {
-            qWarning() << "Command " << static_cast<IrcEventNumeric *>(e)->number() << " requires " << minParams << "params, got: " << e->params();
+            qWarning() << "Command " << static_cast<IrcEventNumeric*>(e)->number() << " requires " << minParams
+                       << "params, got: " << e->params();
         }
         else {
-            QString name = coreSession()->eventManager()->enumName(e->type());
+            QString name = EventManager::enumName(e->type());
             qWarning() << qPrintable(name) << "requires" << minParams << "params, got:" << e->params();
         }
         e->stop();
@@ -69,27 +76,24 @@ bool EventStringifier::checkParamCount(IrcEvent *e, int minParams)
     return true;
 }
 
-
 /* These are only for legacy reasons; remove as soon as we handle NetworkSplitEvents properly */
-void EventStringifier::processNetworkSplitJoin(NetworkSplitEvent *e)
+void EventStringifier::processNetworkSplitJoin(NetworkSplitEvente)
 {
     QString msg = e->users().join("#:#") + "#:#" + e->quitMessage();
     displayMsg(e, Message::NetsplitJoin, msg, QString(), e->channel());
 }
 
-
-void EventStringifier::processNetworkSplitQuit(NetworkSplitEvent *e)
+void EventStringifier::processNetworkSplitQuit(NetworkSplitEvent* e)
 {
     QString msg = e->users().join("#:#") + "#:#" + e->quitMessage();
     displayMsg(e, Message::NetsplitQuit, msg, QString(), e->channel());
 }
 
-
 /* End legacy */
 
-void EventStringifier::processIrcEventNumeric(IrcEventNumeric *e)
+void EventStringifier::processIrcEventNumeric(IrcEventNumerice)
 {
-    //qDebug() << e->number();
+    // qDebug() << e->number();
     switch (e->number()) {
     // Welcome, status, info messages. Just display these.
     case 1:
@@ -104,6 +108,10 @@ void EventStringifier::processIrcEventNumeric(IrcEventNumeric *e)
     case 253:
     case 254:
     case 255:
+    case 256:
+    case 257:
+    case 258:
+    case 259:
     case 265:
     case 266:
     case 372:
@@ -112,6 +120,7 @@ void EventStringifier::processIrcEventNumeric(IrcEventNumeric *e)
         break;
 
     // Server error messages without param, just display them
+    case 263:
     case 409:
     case 411:
     case 412:
@@ -132,13 +141,12 @@ void EventStringifier::processIrcEventNumeric(IrcEventNumeric *e)
     case 491:
     case 501:
     case 502:
-    case 431: // ERR_NONICKNAMEGIVEN
+    case 431:  // ERR_NONICKNAMEGIVEN
         displayMsg(e, Message::Error, e->params().join(" "), e->prefix());
         break;
 
     // Server error messages, display them in red. Colon between first param and rest.
-    case 401:
-    {
+    case 401: {
         if (!checkParamCount(e, 1))
             return;
 
@@ -155,8 +163,7 @@ void EventStringifier::processIrcEventNumeric(IrcEventNumeric *e)
     case 408:
     case 415:
     case 421:
-    case 442:
-    {
+    case 442: {
         if (!checkParamCount(e, 1))
             return;
 
@@ -172,7 +179,7 @@ void EventStringifier::processIrcEventNumeric(IrcEventNumeric *e)
     case 423:
     case 441:
     case 444:
-    case 461:                                                  // FIXME see below for the 47x codes
+    case 461:  // FIXME see below for the 47x codes
     case 467:
     case 471:
     case 473:
@@ -182,7 +189,7 @@ void EventStringifier::processIrcEventNumeric(IrcEventNumeric *e)
     case 477:
     case 478:
     case 482:
-    case 436: // ERR_NICKCOLLISION
+    case 436:  // ERR_NICKCOLLISION
     {
         if (!checkParamCount(e, 1))
             return;
@@ -200,15 +207,29 @@ void EventStringifier::processIrcEventNumeric(IrcEventNumeric *e)
     case 376:
         break;
 
-    // CAP stuff
-    case 900:
-    case 903:
-    case 904:
-    case 905:
-    case 906:
-    case 907:
+    // SASL authentication stuff
+    // See: http://ircv3.net/specs/extensions/sasl-3.1.html
+    case 900:  // RPL_LOGGEDIN
+    case 901:  // RPL_LOGGEDOUT
+    {
+        // :server 900 <nick> <nick>!<ident>@<host> <account> :You are now logged in as <user>
+        // :server 901 <nick> <nick>!<ident>@<host> :You are now logged out
+        if (!checkParamCount(e, 3))
+            return;
+        displayMsg(e, Message::Server, "SASL: " + e->params().at(2));
+        break;
+    }
+    // Ignore SASL success, partially redundant with RPL_LOGGEDIN and RPL_LOGGEDOUT
+    case 903:  // RPL_SASLSUCCESS  :server 903 <nick> :SASL authentication successful
+        break;
+    case 902:  // ERR_NICKLOCKED   :server 902 <nick> :You must use a nick assigned to you
+    case 904:  // ERR_SASLFAIL     :server 904 <nick> :SASL authentication failed
+    case 905:  // ERR_SASLTOOLONG  :server 905 <nick> :SASL message too long
+    case 906:  // ERR_SASLABORTED  :server 906 <nick> :SASL authentication aborted
+    case 907:  // ERR_SASLALREADY  :server 907 <nick> :You have already authenticated using SASL
+    case 908:  // RPL_SASLMECHS    :server 908 <nick> <mechanisms> :are available SASL mechanisms
     {
-        displayMsg(e, Message::Info, "CAP: " + e->params().join(""));
+        displayMsg(e, Message::Server, "SASL: " + e->params().join(""));
         break;
     }
 
@@ -220,48 +241,67 @@ void EventStringifier::processIrcEventNumeric(IrcEventNumeric *e)
         }
         else {
             // FIXME figure out how/where to do this in the future
-            //if(coreSession()->ircListHelper()->requestInProgress(network()->networkId()))
+            // if(coreSession()->ircListHelper()->requestInProgress(network()->networkId()))
             //  coreSession()->ircListHelper()->reportError(params.join(" "));
-            //else
+            // else
             displayMsg(e, Message::Error, QString("%1 %2").arg(e->number(), 3, 10, QLatin1Char('0')).arg(e->params().join(" ")), e->prefix());
         }
     }
 }
 
-
-void EventStringifier::processIrcEventInvite(IrcEvent *e)
+void EventStringifier::processIrcEventInvite(IrcEvent* e)
 {
-    displayMsg(e, Message::Invite, tr("%1 invited you to channel %2").arg(e->nick(), e->params().at(1)));
-}
+    if (!checkParamCount(e, 2))
+        return;
 
+    // TODO: provide a nicer UI for invite notifications
+    QString target = e->params().at(0);
+    QString channel = e->params().at(1);
+    if (e->network()->isMyNick(target)) {
+        displayMsg(e, Message::Invite, tr("%1 invited you to channel %2").arg(e->nick(), channel));
+    }
+    else {
+        displayMsg(e, Message::Invite, tr("%1 invited %2 to channel %3").arg(e->nick(), target, channel));
+    }
+}
 
-void EventStringifier::processIrcEventJoin(IrcEvent *e)
+void EventStringifier::processIrcEventJoin(IrcEvente)
 {
     if (e->testFlag(EventManager::Netsplit))
         return;
 
-    displayMsg(e, Message::Join, e->params()[0], e->prefix(), e->params()[0]);
-}
+    Message::Flag msgFlags = Message::Flag::None;
+    if (e->testFlag(EventManager::Self)) {
+        // Mark the message as Self
+        msgFlags = Message::Self;
+    }
 
+    displayMsg(e, Message::Join, e->params()[0], e->prefix(), e->params()[0], msgFlags);
+}
 
-void EventStringifier::processIrcEventKick(IrcEvent *e)
+void EventStringifier::processIrcEventKick(IrcEvente)
 {
     if (!checkParamCount(e, 2))
         return;
 
-    IrcUser *victim = e->network()->ircUser(e->params().at(1));
+    IrcUservictim = e->network()->ircUser(e->params().at(1));
     if (victim) {
         QString channel = e->params().at(0);
         QString msg = victim->nick();
         if (e->params().count() > 2)
             msg += " " + e->params().at(2);
 
-        displayMsg(e, Message::Kick, msg, e->prefix(), channel);
+        Message::Flag msgFlags = Message::Flag::None;
+        if (e->testFlag(EventManager::Self)) {
+            // Mark the message as Self
+            msgFlags = Message::Self;
+        }
+
+        displayMsg(e, Message::Kick, msg, e->prefix(), channel, msgFlags);
     }
 }
 
-
-void EventStringifier::processIrcEventMode(IrcEvent *e)
+void EventStringifier::processIrcEventMode(IrcEvent* e)
 {
     if (e->network()->isChannelName(e->params().first())) {
         // Channel Modes
@@ -270,33 +310,49 @@ void EventStringifier::processIrcEventMode(IrcEvent *e)
     else {
         // User Modes
         // FIXME: redirect
-        displayMsg(e, Message::Mode, e->params().join(" "), e->prefix());
+
+        Message::Flag msgFlags = Message::Flag::None;
+        if (e->testFlag(EventManager::Self)) {
+            // Mark the message as Self
+            msgFlags = Message::Self;
+        }
+        displayMsg(e, Message::Mode, e->params().join(" "), e->prefix(), QString(), msgFlags);
     }
 }
 
-
 // this needs to be called before the ircuser is renamed!
-void EventStringifier::processIrcEventNick(IrcEvent *e)
+void EventStringifier::processIrcEventNick(IrcEvente)
 {
     if (!checkParamCount(e, 1))
         return;
 
-    IrcUser *ircuser = e->network()->updateNickFromMask(e->prefix());
+    IrcUserircuser = e->network()->updateNickFromMask(e->prefix());
     if (!ircuser) {
         qWarning() << Q_FUNC_INFO << "Unknown IrcUser!";
         return;
     }
 
     QString newnick = e->params().at(0);
-    QString oldnick = ircuser->nick();
 
-    QString sender = e->network()->isMyNick(oldnick) ? newnick : e->prefix();
-    foreach(const QString &channel, ircuser->channels())
-    displayMsg(e, Message::Nick, newnick, sender, channel);
-}
+    QString sender;
+    Message::Flag msgFlags = Message::Flag::None;
+    if (e->testFlag(EventManager::Self)) {
+        // Treat the sender as the new nickname, mark the message as Self
+        sender = newnick;
+        msgFlags = Message::Self;
+    }
+    else {
+        // Take the sender from the event prefix, don't mark the message
+        sender = e->prefix();
+    }
 
+    // Announce to all channels the IrcUser is in
+    for (const QString& channel : ircuser->channels()) {
+        displayMsg(e, Message::Nick, newnick, sender, channel, msgFlags);
+    }
+}
 
-void EventStringifier::processIrcEventPart(IrcEvent *e)
+void EventStringifier::processIrcEventPart(IrcEvente)
 {
     if (!checkParamCount(e, 1))
         return;
@@ -304,56 +360,85 @@ void EventStringifier::processIrcEventPart(IrcEvent *e)
     QString channel = e->params().at(0);
     QString msg = e->params().count() > 1 ? e->params().at(1) : QString();
 
-    displayMsg(e, Message::Part, msg, e->prefix(), channel);
-}
+    Message::Flag msgFlags = Message::Flag::None;
+    if (e->testFlag(EventManager::Self)) {
+        // Mark the message as Self
+        msgFlags = Message::Self;
+    }
 
+    displayMsg(e, Message::Part, msg, e->prefix(), channel, msgFlags);
+}
 
-void EventStringifier::processIrcEventPong(IrcEvent *e)
+void EventStringifier::processIrcEventPong(IrcEvente)
 {
-    QString timestamp = e->params().at(1);
-    QTime sendTime = QTime::fromString(timestamp, "hh:mm:ss.zzz");
-    if (!sendTime.isValid())
-        displayMsg(e, Message::Server, "PONG " + e->params().join(" "), e->prefix());
-}
+    // CoreSessionEventProcessor will flag automated PONG replies as EventManager::Silent.  There's
+    // no need to handle that specially here.
 
+    // Format the PONG reply for display
+    displayMsg(e, Message::Server, "PONG " + e->params().join(" "), e->prefix());
+}
 
-void EventStringifier::processIrcEventQuit(IrcEvent *e)
+void EventStringifier::processIrcEventQuit(IrcEvente)
 {
     if (e->testFlag(EventManager::Netsplit))
         return;
 
-    IrcUser *ircuser = e->network()->updateNickFromMask(e->prefix());
+    IrcUserircuser = e->network()->updateNickFromMask(e->prefix());
     if (!ircuser)
         return;
 
-    foreach(const QString &channel, ircuser->channels())
-    displayMsg(e, Message::Quit, e->params().count() ? e->params().first() : QString(), e->prefix(), channel);
+    Message::Flag msgFlags = Message::Flag::None;
+    if (e->testFlag(EventManager::Self)) {
+        // Mark the message as Self
+        msgFlags = Message::Self;
+    }
+
+    // Announce to all channels the IrcUser is in
+    for (const QString& channel : ircuser->channels()) {
+        displayMsg(e, Message::Quit, e->params().count() ? e->params().first() : QString(), e->prefix(), channel, msgFlags);
+    }
 }
 
+void EventStringifier::processIrcEventTopic(IrcEvent* e)
+{
+    Message::Flag msgFlags = Message::Flag::None;
+    if (e->testFlag(EventManager::Self)) {
+        // Mark the message as Self
+        msgFlags = Message::Self;
+    }
+
+    displayMsg(e,
+               Message::Topic,
+               tr("%1 has changed topic for %2 to: \"%3\"").arg(e->nick(), e->params().at(0), e->params().at(1)),
+               QString(),
+               e->params().at(0),
+               msgFlags);
+}
 
-void EventStringifier::processIrcEventTopic(IrcEvent *e)
+void EventStringifier::processIrcEventError(IrcEvent* e)
 {
-    displayMsg(e, Message::Topic, tr("%1 has changed topic for %2 to: \"%3\"")
-        .arg(e->nick(), e->params().at(0), e->params().at(1)), QString(), e->params().at(0));
+    // Need an error reason
+    if (!checkParamCount(e, 1))
+        return;
+
+    displayMsg(e, Message::Server, tr("Error from server: ") + e->params().join(""));
 }
 
-void EventStringifier::processIrcEventWallops(IrcEvent *e)
+void EventStringifier::processIrcEventWallops(IrcEvente)
 {
     displayMsg(e, Message::Server, tr("[Operwall] %1: %2").arg(e->nick(), e->params().join(" ")));
 }
 
-
 /* RPL_ISUPPORT */
-void EventStringifier::processIrcEvent005(IrcEvent *e)
+void EventStringifier::processIrcEvent005(IrcEvente)
 {
     if (!e->params().last().contains(QRegExp("are supported (by|on) this server")))
         displayMsg(e, Message::Error, tr("Received non-RFC-compliant RPL_ISUPPORT: this can lead to unexpected behavior!"), e->prefix());
     displayMsg(e, Message::Server, e->params().join(" "), e->prefix());
 }
 
-
 /* RPL_AWAY - "<nick> :<away message>" */
-void EventStringifier::processIrcEvent301(IrcEvent *e)
+void EventStringifier::processIrcEvent301(IrcEvente)
 {
     QString nick = e->params().at(0);
     QString awayMsg = e->params().at(1);
@@ -366,35 +451,41 @@ void EventStringifier::processIrcEvent301(IrcEvent *e)
     }
     else {
         target = nick;
-        IrcUser *ircuser = e->network()->ircUser(nick);
+        IrcUserircuser = e->network()->ircUser(nick);
         if (ircuser) {
-            int now = QDateTime::currentDateTime().toTime_t();
-            const int silenceTime = 60;
-            if (ircuser->lastAwayMessage() + silenceTime >= now)
+            QDateTime now = QDateTime::currentDateTime();
+            now.setTimeSpec(Qt::UTC);
+            // Don't print "user is away" messages more often than this
+            // 1 hour = 60 min * 60 sec
+            const int silenceTime = 60 * 60;
+            // Check if away state has NOT changed and silence time hasn't yet elapsed
+            if (!ircuser->hasAwayChanged() && ircuser->lastAwayMessageTime().addSecs(silenceTime) >= now) {
+                // Away message hasn't changed and we're still within the period of silence; don't
+                // repeat the message
                 send = false;
-            ircuser->setLastAwayMessage(now);
+            }
+            ircuser->setLastAwayMessageTime(now);
+            // Mark any changes in away as acknowledged
+            ircuser->acknowledgeAwayChanged();
         }
     }
     if (send)
         displayMsg(e, Message::Server, msg + tr("%1 is away: \"%2\"").arg(nick, awayMsg), QString(), target);
 }
 
-
 /* RPL_UNAWAY - ":You are no longer marked as being away" */
-void EventStringifier::processIrcEvent305(IrcEvent *e)
+void EventStringifier::processIrcEvent305(IrcEvente)
 {
     displayMsg(e, Message::Server, tr("You are no longer marked as being away"));
 }
 
-
 /* RPL_NOWAWAY - ":You have been marked as being away" */
-void EventStringifier::processIrcEvent306(IrcEvent *e)
+void EventStringifier::processIrcEvent306(IrcEvente)
 {
     if (!e->network()->autoAwayActive())
         displayMsg(e, Message::Server, tr("You have been marked as being away"));
 }
 
-
 /*
 WHOIS-Message:
    Replies 311 - 313, 317 - 319 are all replies generated in response to a WHOIS message.
@@ -409,13 +500,13 @@ WHOWAS-Message:
 */
 
 /*  RPL_WHOISUSER - "<nick> <user> <host> * :<real name>" */
-void EventStringifier::processIrcEvent311(IrcEvent *e)
+void EventStringifier::processIrcEvent311(IrcEvente)
 {
     _whois = true;
 
     const QString whoisUserString = tr("[Whois] %1 is %2 (%3)");
 
-    IrcUser *ircuser = e->network()->ircUser(e->params().at(0));
+    IrcUserircuser = e->network()->ircUser(e->params().at(0));
     if (ircuser)
         displayMsg(e, Message::Server, whoisUserString.arg(ircuser->nick(), ircuser->hostmask(), ircuser->realName()));
     else {
@@ -424,9 +515,8 @@ void EventStringifier::processIrcEvent311(IrcEvent *e)
     }
 }
 
-
 /*  RPL_WHOISSERVER -  "<nick> <server> :<server info>" */
-void EventStringifier::processIrcEvent312(IrcEvent *e)
+void EventStringifier::processIrcEvent312(IrcEvente)
 {
     if (_whois)
         displayMsg(e, Message::Server, tr("[Whois] %1 is online via %2 (%3)").arg(e->params().at(0), e->params().at(1), e->params().last()));
@@ -434,9 +524,8 @@ void EventStringifier::processIrcEvent312(IrcEvent *e)
         displayMsg(e, Message::Server, tr("[Whowas] %1 was online via %2 (%3)").arg(e->params().at(0), e->params().at(1), e->params().last()));
 }
 
-
 /*  RPL_WHOWASUSER - "<nick> <user> <host> * :<real name>" */
-void EventStringifier::processIrcEvent314(IrcEvent *e)
+void EventStringifier::processIrcEvent314(IrcEvente)
 {
     if (!checkParamCount(e, 3))
         return;
@@ -444,44 +533,51 @@ void EventStringifier::processIrcEvent314(IrcEvent *e)
     displayMsg(e, Message::Server, tr("[Whowas] %1 was %2@%3 (%4)").arg(e->params()[0], e->params()[1], e->params()[2], e->params().last()));
 }
 
-
 /*  RPL_ENDOFWHO: "<name> :End of WHO list" */
-void EventStringifier::processIrcEvent315(IrcEvent *e)
+void EventStringifier::processIrcEvent315(IrcEvente)
 {
     QStringList p = e->params();
-    p.takeLast(); // should be "End of WHO list"
+    p.takeLast();  // should be "End of WHO list"
     displayMsg(e, Message::Server, tr("[Who] End of /WHO list for %1").arg(p.join(" ")));
 }
 
-
 /*  RPL_WHOISIDLE - "<nick> <integer> :seconds idle"
    (real life: "<nick> <integer> <integer> :seconds idle, signon time) */
-void EventStringifier::processIrcEvent317(IrcEvent *e)
+void EventStringifier::processIrcEvent317(IrcEvente)
 {
     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
-        QDateTime loginTime = QDateTime::fromTime_t(e->params()[2].toInt());
-        displayMsg(e, Message::Server, tr("[Whois] %1 is logged in since %2")
-           .arg(e->params()[0], QLocale().toString(loginTime, QLocale().dateTimeFormat())));
+    if (e->params().count() > 3) {
+        // if we have more then 3 params we have the above mentioned "real life" situation
+        // 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
+        QDateTime loginTime = QDateTime::fromSecsSinceEpoch(e->params()[2].toLongLong()).toUTC();
+#else
+        // fromSecsSinceEpoch() was added in Qt 5.8.  Manually downconvert to seconds for now.
+        // See https://doc.qt.io/qt-5/qdatetime.html#fromMSecsSinceEpoch
+        QDateTime loginTime = QDateTime::fromMSecsSinceEpoch((qint64)(e->params()[2].toLongLong() * 1000)).toUTC();
+#endif
+        displayMsg(e,
+                   Message::Server,
+                   tr("[Whois] %1 is logged in since %2").arg(e->params()[0], loginTime.toString("yyyy-MM-dd hh:mm:ss UTC")));
     }
-    QDateTime idlingSince = e->timestamp().toLocalTime().addSecs(-idleSecs);
-    displayMsg(e, Message::Server, tr("[Whois] %1 is idling for %2 (since %3)")
-        .arg(e->params()[0], secondsToString(idleSecs),
-            QLocale().toString(idlingSince, QLocale().dateTimeFormat())));
+    QDateTime idlingSince = e->timestamp().toLocalTime().addSecs(-idleSecs).toUTC();
+    displayMsg(e,
+               Message::Server,
+               tr("[Whois] %1 is idling for %2 (since %3)")
+                   .arg(e->params()[0], secondsToString(idleSecs), idlingSince.toString("yyyy-MM-dd hh:mm:ss UTC")));
 }
 
-
 /*  RPL_ENDOFWHOIS - "<nick> :End of WHOIS list" */
-void EventStringifier::processIrcEvent318(IrcEvent *e)
+void EventStringifier::processIrcEvent318(IrcEvente)
 {
     _whois = false;
     displayMsg(e, Message::Server, tr("[Whois] End of /WHOIS list"));
 }
 
-
 /*  RPL_WHOISCHANNELS - "<nick> :*( ( "@" / "+" ) <channel> " " )" */
-void EventStringifier::processIrcEvent319(IrcEvent *e)
+void EventStringifier::processIrcEvent319(IrcEvente)
 {
     if (!checkParamCount(e, 2))
         return;
@@ -490,7 +586,7 @@ void EventStringifier::processIrcEvent319(IrcEvent *e)
     QStringList op;
     QStringList voice;
     QStringList user;
-    foreach(QString channel, e->params().last().split(" ")) {
+    for (QString channel : e->params().last().split(" ")) {
         if (channel.startsWith("@"))
             op.append(channel.remove(0, 1));
         else if (channel.startsWith("+"))
@@ -506,9 +602,8 @@ void EventStringifier::processIrcEvent319(IrcEvent *e)
         displayMsg(e, Message::Server, tr("[Whois] %1 is an operator on channels: %2").arg(nick, op.join(" ")));
 }
 
-
 /* RPL_LIST -  "<channel> <# visible> :<topic>" */
-void EventStringifier::processIrcEvent322(IrcEvent *e)
+void EventStringifier::processIrcEvent322(IrcEvente)
 {
     QString channelName;
     quint32 userCount = 0;
@@ -517,34 +612,33 @@ void EventStringifier::processIrcEvent322(IrcEvent *e)
     switch (e->params().count()) {
     case 3:
         topic = e->params()[2];
+        // fallthrough
     case 2:
         userCount = e->params()[1].toUInt();
+        /* fallthrough */
     case 1:
         channelName = e->params()[0];
+        // blubb
     default:
         break;
     }
-    displayMsg(e, Message::Server, tr("Channel %1 has %2 users. Topic is: \"%3\"")
-        .arg(channelName).arg(userCount).arg(topic));
+    displayMsg(e, Message::Server, tr("Channel %1 has %2 users. Topic is: \"%3\"").arg(channelName).arg(userCount).arg(topic));
 }
 
-
 /* RPL_LISTEND ":End of LIST" */
-void EventStringifier::processIrcEvent323(IrcEvent *e)
+void EventStringifier::processIrcEvent323(IrcEvente)
 {
     displayMsg(e, Message::Server, tr("End of channel list"));
 }
 
-
 /* RPL_CHANNELMODEIS - "<channel> <mode> <mode params>" */
-void EventStringifier::processIrcEvent324(IrcEvent *e)
+void EventStringifier::processIrcEvent324(IrcEvente)
 {
     processIrcEventMode(e);
 }
 
-
 /* RPL_??? - "<channel> <homepage> */
-void EventStringifier::processIrcEvent328(IrcEvent *e)
+void EventStringifier::processIrcEvent328(IrcEvente)
 {
     if (!checkParamCount(e, 2))
         return;
@@ -553,28 +647,33 @@ void EventStringifier::processIrcEvent328(IrcEvent *e)
     displayMsg(e, Message::Topic, tr("Homepage for %1 is %2").arg(channel, e->params()[1]), QString(), channel);
 }
 
-
 /* RPL_??? - "<channel> <creation time (unix)>" */
-void EventStringifier::processIrcEvent329(IrcEvent *e)
+void EventStringifier::processIrcEvent329(IrcEvente)
 {
     if (!checkParamCount(e, 2))
         return;
 
     QString channel = e->params()[0];
-    uint unixtime = e->params()[1].toUInt();
+    // Allow for 64-bit time
+    qint64 unixtime = e->params()[1].toLongLong();
     if (!unixtime) {
         qWarning() << Q_FUNC_INFO << "received invalid timestamp:" << e->params()[1];
         return;
     }
-    QDateTime time = QDateTime::fromTime_t(unixtime);
-    displayMsg(e, Message::Topic, tr("Channel %1 created on %2")
-        .arg(channel, QLocale().toString(time, QLocale().dateTimeFormat())),
-       QString(), channel);
+    // 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
+    QDateTime time = QDateTime::fromSecsSinceEpoch(unixtime).toUTC();
+#else
+    // fromSecsSinceEpoch() was added in Qt 5.8.  Manually downconvert to seconds for now.
+    // See https://doc.qt.io/qt-5/qdatetime.html#fromMSecsSinceEpoch
+    QDateTime time = QDateTime::fromMSecsSinceEpoch((qint64)(unixtime * 1000)).toUTC();
+#endif
+    displayMsg(e, Message::Topic, tr("Channel %1 created on %2").arg(channel, time.toString("yyyy-MM-dd hh:mm:ss UTC")), QString(), channel);
 }
 
-
 /*  RPL_WHOISACCOUNT: "<nick> <account> :is authed as */
-void EventStringifier::processIrcEvent330(IrcEvent *e)
+void EventStringifier::processIrcEvent330(IrcEvente)
 {
     if (e->params().count() < 3)
         return;
@@ -588,39 +687,45 @@ void EventStringifier::processIrcEvent330(IrcEvent *e)
     }
 }
 
-
 /* RPL_NOTOPIC */
-void EventStringifier::processIrcEvent331(IrcEvent *e)
+void EventStringifier::processIrcEvent331(IrcEvente)
 {
     QString channel = e->params().first();
     displayMsg(e, Message::Topic, tr("No topic is set for %1.").arg(channel), QString(), channel);
 }
 
-
 /* RPL_TOPIC */
-void EventStringifier::processIrcEvent332(IrcEvent *e)
+void EventStringifier::processIrcEvent332(IrcEvente)
 {
     QString channel = e->params().first();
     displayMsg(e, Message::Topic, tr("Topic for %1 is \"%2\"").arg(channel, e->params()[1]), QString(), channel);
 }
 
-
 /* Topic set by... */
-void EventStringifier::processIrcEvent333(IrcEvent *e)
+void EventStringifier::processIrcEvent333(IrcEvente)
 {
     if (!checkParamCount(e, 3))
         return;
 
     QString channel = e->params().first();
-    QDateTime topicSetTime = QDateTime::fromTime_t(e->params()[2].toInt());
-    displayMsg(e, Message::Topic, tr("Topic set by %1 on %2")
-        .arg(e->params()[1],
-            QLocale().toString(topicSetTime, QLocale().dateTimeFormat())), QString(), channel);
+    // 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
+    QDateTime topicSetTime = QDateTime::fromSecsSinceEpoch(e->params()[2].toLongLong()).toUTC();
+#else
+    // fromSecsSinceEpoch() was added in Qt 5.8.  Manually downconvert to seconds for now.
+    // See https://doc.qt.io/qt-5/qdatetime.html#fromMSecsSinceEpoch
+    QDateTime topicSetTime = QDateTime::fromMSecsSinceEpoch((qint64)(e->params()[2].toLongLong() * 1000)).toUTC();
+#endif
+    displayMsg(e,
+               Message::Topic,
+               tr("Topic set by %1 on %2").arg(e->params()[1], topicSetTime.toString("yyyy-MM-dd hh:mm:ss UTC")),
+               QString(),
+               channel);
 }
 
-
 /* RPL_INVITING - "<nick> <channel>*/
-void EventStringifier::processIrcEvent341(IrcEvent *e)
+void EventStringifier::processIrcEvent341(IrcEvente)
 {
     if (!checkParamCount(e, 2))
         return;
@@ -629,24 +734,30 @@ void EventStringifier::processIrcEvent341(IrcEvent *e)
     displayMsg(e, Message::Server, tr("%1 has been invited to %2").arg(e->params().first(), channel), QString(), channel);
 }
 
-
 /*  RPL_WHOREPLY: "<channel> <user> <host> <server> <nick>
               ( "H" / "G" > ["*"] [ ( "@" / "+" ) ] :<hopcount> <real name>" */
-void EventStringifier::processIrcEvent352(IrcEvent *e)
+void EventStringifier::processIrcEvent352(IrcEvente)
 {
     displayMsg(e, Message::Server, tr("[Who] %1").arg(e->params().join(" ")));
 }
 
+/*  RPL_WHOSPCRPL: "<yournick> <num> #<channel> ~<ident> <host> <servname> <nick>
+                    ("H"/ "G") <account> :<realname>"
+Could be anything else, though.  User-specified fields.
+See http://faerion.sourceforge.net/doc/irc/whox.var */
+void EventStringifier::processIrcEvent354(IrcEvent* e)
+{
+    displayMsg(e, Message::Server, tr("[WhoX] %1").arg(e->params().join(" ")));
+}
 
 /*  RPL_ENDOFWHOWAS - "<nick> :End of WHOWAS" */
-void EventStringifier::processIrcEvent369(IrcEvent *e)
+void EventStringifier::processIrcEvent369(IrcEvente)
 {
     displayMsg(e, Message::Server, tr("End of /WHOWAS"));
 }
 
-
 /* ERR_ERRONEUSNICKNAME */
-void EventStringifier::processIrcEvent432(IrcEvent *e)
+void EventStringifier::processIrcEvent432(IrcEvente)
 {
     if (!checkParamCount(e, 1))
         return;
@@ -654,9 +765,8 @@ void EventStringifier::processIrcEvent432(IrcEvent *e)
     displayMsg(e, Message::Error, tr("Nick %1 contains illegal characters").arg(e->params()[0]));
 }
 
-
 /* ERR_NICKNAMEINUSE */
-void EventStringifier::processIrcEvent433(IrcEvent *e)
+void EventStringifier::processIrcEvent433(IrcEvente)
 {
     if (!checkParamCount(e, 1))
         return;
@@ -664,9 +774,8 @@ void EventStringifier::processIrcEvent433(IrcEvent *e)
     displayMsg(e, Message::Error, tr("Nick already in use: %1").arg(e->params()[0]));
 }
 
-
 /* ERR_UNAVAILRESOURCE */
-void EventStringifier::processIrcEvent437(IrcEvent *e)
+void EventStringifier::processIrcEvent437(IrcEvente)
 {
     if (!checkParamCount(e, 1))
         return;
@@ -674,7 +783,6 @@ void EventStringifier::processIrcEvent437(IrcEvent *e)
     displayMsg(e, Message::Error, tr("Nick/channel is temporarily unavailable: %1").arg(e->params()[0]));
 }
 
-
 // template
 /*
 
@@ -688,47 +796,67 @@ void EventStringifier::processIrcEvent(IrcEvent *e) {
 /******** CTCP HANDLING ********/
 /*******************************/
 
-void EventStringifier::processCtcpEvent(CtcpEvent *e)
+void EventStringifier::processCtcpEvent(CtcpEvente)
 {
     if (e->type() != EventManager::CtcpEvent)
         return;
 
-    if (e->testFlag(EventManager::Self)) {
-        displayMsg(e, Message::Action, tr("sending CTCP-%1 request to %2").arg(e->ctcpCmd(), e->target()), e->network()->myNick());
+    // 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()),
+                   e->network()->myNick(),
+                   QString(),
+                   Message::Flag::Self);
         return;
     }
 
-    handle(e->ctcpCmd(), Q_ARG(CtcpEvent *, e));
+    handle(e->ctcpCmd(), Q_ARG(CtcpEvent*, e));
 }
 
-
-void EventStringifier::defaultHandler(const QString &ctcpCmd, CtcpEvent *e)
+void EventStringifier::defaultHandler(const QString& ctcpCmd, CtcpEvent* e)
 {
     Q_UNUSED(ctcpCmd);
     if (e->ctcpType() == CtcpEvent::Query) {
         QString unknown;
-        if (e->reply().isNull()) // all known core-side handlers (except for ACTION) set a reply!
+        if (e->reply().isNull())  // all known core-side handlers (except for ACTION) set a reply!
             //: 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)
+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(CtcpEvente)
 {
     if (e->ctcpType() == CtcpEvent::Query)
         defaultHandler(e->ctcpCmd(), e);
     else {
-        displayMsg(e, Message::Server, tr("Received CTCP-PING answer from %1 with %2 seconds round trip time")
-            .arg(nickFromMask(e->prefix())).arg(QDateTime::fromTime_t(e->param().toInt()).secsTo(e->timestamp())));
+        displayMsg(e,
+                   Message::Server,
+                   tr("Received CTCP-PING answer from %1 with %2 milliseconds round trip time")
+                       .arg(nickFromMask(e->prefix()))
+                       .arg(QDateTime::fromMSecsSinceEpoch(e->param().toULongLong()).msecsTo(e->timestamp())));
     }
 }