diff --git a/binaries/data/mods/public/gui/lobby/lobby.js b/binaries/data/mods/public/gui/lobby/lobby.js index ccc074d916..0b37c4cb4d 100644 --- a/binaries/data/mods/public/gui/lobby/lobby.js +++ b/binaries/data/mods/public/gui/lobby/lobby.js @@ -135,12 +135,14 @@ var g_NetMessageTypes = { if (!g_Kicked) addChatMessage({ "from": "system", + "time": msg.time, "text": translate("Disconnected.") + " " + msg.text }); }, "error": msg => { addChatMessage({ "from": "system", + "time": msg.time, "text": msg.text }); } @@ -154,6 +156,7 @@ var g_NetMessageTypes = { "text": "/special " + sprintf(translate("%(nick)s has joined."), { "nick": msg.text }), + "time": msg.time, "isSpecial": true }); }, @@ -162,6 +165,7 @@ var g_NetMessageTypes = { "text": "/special " + sprintf(translate("%(nick)s has left."), { "nick": msg.text }), + "time": msg.time, "isSpecial": true }); @@ -194,6 +198,7 @@ var g_NetMessageTypes = { addChatMessage({ "text": "/special " + sprintf(txt, { "nick": msg.text }), + "time": msg.time, "isSpecial": true }); @@ -210,6 +215,7 @@ var g_NetMessageTypes = { "oldnick": msg.text, "newnick": msg.data }), + "time": msg.time, "isSpecial": true }); }, @@ -223,7 +229,7 @@ var g_NetMessageTypes = { addChatMessage({ "from": escapeText(msg.from), "text": escapeText(msg.text), - "datetime": msg.datetime + "time": msg.time }); }, "private-message": msg => { @@ -232,7 +238,7 @@ var g_NetMessageTypes = { addChatMessage({ "from": escapeText(msg.from), "text": escapeText(msg.text.trim()), - "datetime": msg.datetime, + "time": msg.time, "private" : true }); } @@ -492,6 +498,7 @@ function handleKick(banned, nick, reason) { addChatMessage({ "text": "/special " + sprintf(kickString, { "nick": nick }) + " " + reason, + "time": msg.time, "isSpecial": true }); return; @@ -499,6 +506,7 @@ function handleKick(banned, nick, reason) addChatMessage({ "from": "system", + "time": msg.time, "text": kickString + " " + reason, }); @@ -1333,24 +1341,10 @@ function ircFormat(msg) if (Engine.ConfigDB_GetValue("user", "chat.timestamp") != "true") return formattedMessage; - let time; - if (msg.datetime) - { - let dTime = msg.datetime.split("T"); - let parserDate = dTime[0].split("-"); - let parserTime = dTime[1].split(":"); - // See http://xmpp.org/extensions/xep-0082.html#sect-idp285136 for format of datetime - // Date takes Year, Month, Day, Hour, Minute, Second - time = new Date(Date.UTC(parserDate[0], parserDate[1], parserDate[2], - parserTime[0], parserTime[1], parserTime[2].split("Z")[0])); - } - else - time = new Date(Date.now()); - // Translation: Time as shown in the multiplayer lobby (when you enable it in the options page). // For a list of symbols that you can use, see: // https://sites.google.com/site/icuprojectuserguide/formatparse/datetime?pli=1#TOC-Date-Field-Symbol-Table - let timeString = Engine.FormatMillisecondsIntoDateStringLocal(time.getTime(), translate("HH:mm")); + let timeString = Engine.FormatMillisecondsIntoDateStringLocal(msg.time ? msg.time * 1000 : Date.now(), translate("HH:mm")); // Translation: Time prefix as shown in the multiplayer lobby (when you enable it in the options page). let timePrefixString = sprintf(translate("\\[%(time)s]"), { diff --git a/source/lobby/XmppClient.cpp b/source/lobby/XmppClient.cpp index f60a68bed8..bc255c6be2 100644 --- a/source/lobby/XmppClient.cpp +++ b/source/lobby/XmppClient.cpp @@ -23,8 +23,12 @@ # include #endif -#include "glooxwrapper/glooxwrapper.h" #include "i18n/L10n.h" + +#if OS_WIN +#include "lib/sysdep/os/win/wposix/wtime.h" +#endif + #include "lib/external_libraries/enet.h" #include "lib/utf8.h" #include "network/NetServer.h" @@ -574,8 +578,8 @@ void XmppClient::GuiPollMessage(ScriptInterface& scriptInterface, JS::MutableHan scriptInterface.SetProperty(ret, "level", message.level); if (!message.data.empty()) scriptInterface.SetProperty(ret, "data", message.data); - if (!message.datetime.empty()) - scriptInterface.SetProperty(ret, "datetime", message.datetime); + + scriptInterface.SetProperty(ret, "time", (double)message.time); m_GuiMessageQueue.pop_front(); } @@ -637,9 +641,7 @@ void XmppClient::handleMUCMessage(glooxwrapper::MUCRoom*, const glooxwrapper::Me message.level = priv ? L"private-message" : L"room-message"; message.from = wstring_from_utf8(msg.from().resource().to_string()); message.text = wstring_from_utf8(msg.body().to_string()); - if (msg.when()) - // See http://xmpp.org/extensions/xep-0082.html#sect-idp285136 for format - message.datetime = msg.when()->stamp().to_string(); + message.time = ComputeTimestamp(msg); PushGuiMessage(message); } @@ -656,9 +658,7 @@ void XmppClient::handleMessage(const glooxwrapper::Message& msg, glooxwrapper::M message.level = L"private-message"; message.from = wstring_from_utf8(msg.from().username().to_string()); message.text = wstring_from_utf8(msg.body().to_string()); - if (msg.when()) - //See http://xmpp.org/extensions/xep-0082.html#sect-idp285136 for format - message.datetime = msg.when()->stamp().to_string(); + message.time = ComputeTimestamp(msg); PushGuiMessage(message); } @@ -751,6 +751,7 @@ void XmppClient::CreateGUIMessage(const std::string& type, const std::string& le message.level = wstring_from_utf8(level); message.text = wstring_from_utf8(text); message.data = wstring_from_utf8(data); + message.time = std::time(nullptr); PushGuiMessage(message); } @@ -935,6 +936,28 @@ void XmppClient::GetRole(const std::string& nick, std::string& role) * Utilities * *****************************************************/ +/** + * Compute the POSIX timestamp of a message. Uses message datetime when possible, current time otherwise. + * + * @param msg The message on which to base the computation. + * @returns Seconds since the epoch. + */ +std::time_t XmppClient::ComputeTimestamp(const glooxwrapper::Message& msg) const +{ + // Only historic messages contain a timestamp! + if (!msg.when()) + return std::time(nullptr); + + glooxwrapper::string timestampStr = msg.when()->stamp(); + struct tm timestamp = {0}; + + // See http://xmpp.org/extensions/xep-0082.html#sect-idp285136 for format + if (!strptime(timestampStr.c_str(), "%Y-%m-%dT%H:%M:%SZ", ×tamp)) + LOGERROR("Received delayed message with corrupted timestamp %s", timestampStr.to_string()); + + return std::mktime(×tamp) - timezone; +} + /** * Convert a gloox presence type to string. * diff --git a/source/lobby/XmppClient.h b/source/lobby/XmppClient.h index 5c7d81f0e8..285ed177dc 100644 --- a/source/lobby/XmppClient.h +++ b/source/lobby/XmppClient.h @@ -132,6 +132,7 @@ protected: std::string StanzaErrorToString(gloox::StanzaError err) const; std::string ConnectionErrorToString(gloox::ConnectionError err) const; std::string RegistrationResultToString(gloox::RegistrationResult res) const; + std::time_t ComputeTimestamp(const glooxwrapper::Message& msg) const; public: /* Messages */ @@ -143,7 +144,7 @@ public: std::wstring data; std::wstring from; std::wstring message; - std::string datetime; + std::time_t time; }; void GuiPollMessage(ScriptInterface& scriptInterface, JS::MutableHandleValue ret); void SendMUCMessage(const std::string& message);