diff --git a/binaries/data/mods/public/gui/lobby/lobby.js b/binaries/data/mods/public/gui/lobby/lobby.js index 572f5f495f..48269746d5 100644 --- a/binaries/data/mods/public/gui/lobby/lobby.js +++ b/binaries/data/mods/public/gui/lobby/lobby.js @@ -111,10 +111,20 @@ var g_NetMessageTypes = { updateLeaderboard(); updatePlayerList(); Engine.GetGUIObjectByName("hostButton").enabled = false; - addChatMessage({ "from": "system", "text": translate("Disconnected.") + msg.text, "color": g_SystemColor }); + addChatMessage({ + "from": "system", + "text": translate("Disconnected.") + msg.text, + "color": g_SystemColor, + "time": msg.time + }); }, "error": msg => { - addChatMessage({ "from": "system", "text": msg.text, "color": g_SystemColor }); + addChatMessage({ + "from": "system", + "text": msg.text, + "color": g_SystemColor, + "time": msg.time + }); } }, "chat": { @@ -124,14 +134,16 @@ var g_NetMessageTypes = { "join": msg => { addChatMessage({ "text": "/special " + sprintf(translate("%(nick)s has joined."), { "nick": msg.text }), - "isSpecial": true + "isSpecial": true, + "time": msg.time }); Engine.SendGetRatingList(); }, "leave": msg => { addChatMessage({ "text": "/special " + sprintf(translate("%(nick)s has left."), { "nick": msg.text }), - "isSpecial": true + "isSpecial": true, + "time": msg.time }); }, "presence": msg => { @@ -142,14 +154,15 @@ var g_NetMessageTypes = { "oldnick": msg.text, "newnick": msg.data }), - "isSpecial": true + "isSpecial": true, + "time": msg.time }); }, "room-message": msg => { addChatMessage({ "from": escapeText(msg.from), "text": escapeText(msg.text), - "datetime": msg.datetime + "time": msg.time }); }, "private-message": msg => { @@ -157,7 +170,7 @@ var g_NetMessageTypes = { addChatMessage({ "from": "(Private) " + escapeText(msg.from), // TODO: placeholder "text": escapeText(msg.text.trim()), // some XMPP clients send trailing whitespace - "datetime": msg.datetime + "time": msg.time }); } }, @@ -658,7 +671,8 @@ function joinSelectedGame() "text": sprintf( translate("This game's address '%(ip)s' does not appear to be valid."), { "ip": game.ip } - ) + ), + "time": Date.now() / 1000 }); return; } @@ -769,7 +783,8 @@ function handleSpecialCommand(text) default: addChatMessage({ "from": "system", - "text": sprintf(translate("We're sorry, the '%(cmd)s' command is not supported."), { "cmd": cmd }) + "text": sprintf(translate("We're sorry, the '%(cmd)s' command is not supported."), { "cmd": cmd }), + "time": Date.now() / 1000 }); } return true; @@ -791,13 +806,10 @@ function addChatMessage(msg) if (g_Username != msg.from) msg.text = msg.text.replace(g_Username, colorPlayerName(g_Username)); - // Run spam test if it's not a historical message - if (!msg.datetime) - { - updateSpamMonitor(msg.from); - if (isSpam(msg.text, msg.from)) - return; - } + updateSpamMonitor(msg); + + if (isSpam(msg.text, msg.from)) + return; } var formatted = ircFormat(msg); @@ -907,14 +919,18 @@ function ircFormat(msg) /** * Update the spam monitor. * - * @param {string} from - User to update. + * @param {Object} msg - Message containing user to update. */ -function updateSpamMonitor(from) +function updateSpamMonitor(msg) { - if (g_SpamMonitor[from]) - ++g_SpamMonitor[from][0]; + // Ignore historical messages + if (msg.time < Date.now() / 1000 - g_SpamBlockDuration) + return; + + if (g_SpamMonitor[msg.from]) + ++g_SpamMonitor[msg.from][0]; else - g_SpamMonitor[from] = [1, Math.floor(Date.now() / 1000), 0]; + g_SpamMonitor[msg.from] = [1, Math.floor(Date.now() / 1000), 0]; } /** @@ -947,7 +963,11 @@ function isSpam(text, from) { g_SpamMonitor[from][2] = time; if (from == g_Username) - addChatMessage({ "from": "system", "text": translate("Please do not spam. You have been blocked for thirty seconds.") }); + addChatMessage({ + "from": "system", + "text": translate("Please do not spam. You have been blocked for thirty seconds."), + "time": Date.now() / 1000 + }); return true; } diff --git a/source/lobby/XmppClient.cpp b/source/lobby/XmppClient.cpp index f030040342..31e8674f10 100644 --- a/source/lobby/XmppClient.cpp +++ b/source/lobby/XmppClient.cpp @@ -592,8 +592,7 @@ 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", message.time); m_GuiMessageQueue.pop_front(); } @@ -655,9 +654,7 @@ void XmppClient::handleMUCMessage(glooxwrapper::MUCRoom*, const glooxwrapper::Me message.level = 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); } @@ -674,9 +671,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); } @@ -769,6 +764,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 = time(NULL); PushGuiMessage(message); } @@ -938,6 +934,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 POSIX GMT/UTC seconds since Jan. 1st 1970 + */ +time_t XmppClient::ComputeTimestamp(const glooxwrapper::Message& msg) +{ + if (!msg.when()) + return time(NULL); + + glooxwrapper::string timestampStr = msg.when()->stamp(); + struct tm timestamp = {0}; + + // See http://xmpp.org/extensions/xep-0082.html#sect-idp285136 for format + void * res = strptime(timestampStr.c_str(), "%Y-%m-%dT%H:%M:%SZ", ×tamp); + if (res == NULL) + LOGERROR("Recived delayed message with corrupted timestamp %s", timestampStr.to_string()); + + return mktime(×tamp) - timezone; +} + /** * Convert a gloox presence type to string. * diff --git a/source/lobby/XmppClient.h b/source/lobby/XmppClient.h index 1d25e6d0a6..0094467498 100644 --- a/source/lobby/XmppClient.h +++ b/source/lobby/XmppClient.h @@ -127,6 +127,7 @@ protected: std::string StanzaErrorToString(gloox::StanzaError err) const; std::string ConnectionErrorToString(gloox::ConnectionError err) const; std::string RegistrationResultToString(gloox::RegistrationResult res) const; + time_t ComputeTimestamp(const glooxwrapper::Message& msg); public: /* Messages */ @@ -138,13 +139,13 @@ public: std::wstring data; std::wstring from; std::wstring message; - std::string datetime; + time_t time; }; void GuiPollMessage(ScriptInterface& scriptInterface, JS::MutableHandleValue ret); void SendMUCMessage(const std::string& message); void ClearPresenceUpdates(); int GetMucMessageCount(); - protected: +protected: void PushGuiMessage(XmppClient::GUIMessage message); void CreateGUIMessage(const std::string& type, const std::string& level, const std::string& text = "", const std::string& data = "");