diff --git a/binaries/data/mods/public/gui/lobby/lobby.js b/binaries/data/mods/public/gui/lobby/lobby.js index f9e4328440..eb09051f7d 100644 --- a/binaries/data/mods/public/gui/lobby/lobby.js +++ b/binaries/data/mods/public/gui/lobby/lobby.js @@ -10,6 +10,7 @@ const g_mapTypesText = [translateWithContext("map", "Skirmish"), translateWithCo const g_mapTypes = ["skirmish", "random", "scenario"]; var g_userRating = ""; // Rating of user, defaults to Unrated var g_modPrefix = "@"; +var g_joined = false; // Block spammers for 30 seconds. var SPAM_BLOCK_LENGTH = 30; @@ -613,11 +614,11 @@ function onTick() { case "mucmessage": // For room messages var from = escapeText(message.from); - addChatMessage({ "from": from, "text": text }); + addChatMessage({ "from": from, "text": text, "datetime": message.datetime}); break; case "message": // For private messages var from = escapeText(message.from); - addChatMessage({ "from": from, "text": text }); + addChatMessage({ "from": from, "text": text, "datetime": message.datetime}); break; case "muc": var nick = message.text; @@ -635,15 +636,21 @@ function onTick() { // We just joined, we need to get the full player list [playerList, presenceList, nickList, ratingList] = updatePlayerList(); + // Don't display any joins until our join request bounces back + // Our join message should be the last one as we just got added to the stack + g_joined = true; break; } - var [name, status, rating] = formatPlayerListEntry(nick, presence, "-"); - playerList.push(name); - presenceList.push(status); - nickList.push(nick); - ratingList.push(String(rating)); - Engine.SendGetRatingList(); - addChatMessage({ "text": "/special " + sprintf(translate("%(nick)s has joined."), { nick: nick }), "key": g_specialKey }); + else if (g_joined) + { + var [name, status, rating] = formatPlayerListEntry(nick, presence, "-"); + playerList.push(name); + presenceList.push(status); + nickList.push(nick); + ratingList.push(String(rating)); + Engine.SendGetRatingList(); + addChatMessage({ "text": "/special " + sprintf(translate("%(nick)s has joined."), { nick: nick }), "key": g_specialKey }); + } break; case "leave": if (nickIndex == -1) // Left, but not present (TODO: warn about this?) @@ -819,19 +826,22 @@ function addChatMessage(msg) if (!msg.color) msg.color = null; if (!msg.key) - msg.key = null; + msg.key = null; + if (!msg.datetime) + msg.datetime = null; // Highlight local user's nick if (msg.text.indexOf(g_Name) != -1 && g_Name != msg.from) msg.text = msg.text.replace(new RegExp('\\b' + '\\' + g_Name + '\\b', "g"), colorPlayerName(g_Name)); - // Run spam test - updateSpamMonitor(msg.from); + // Run spam test if it's not a historical message + if (!msg.datetime) + updateSpamMonitor(msg.from); if (isSpam(msg.text, msg.from)) return; // Format Text - var formatted = ircFormat(msg.text, msg.from, msg.color, msg.key); + var formatted = ircFormat(msg.text, msg.from, msg.color, msg.key, msg.datetime); // If there is text, add it to the chat box. if (formatted) @@ -856,9 +866,10 @@ function ircSplit(string) * @param from Sender of the message. * @param color Optional color of sender. * @param key Key to verify join/leave messages with. TODO: Remove this, it only provides synthetic security. + * @param datetime Current date and time of message, only used for historical messages * @return Formatted text. */ -function ircFormat(text, from, color, key) +function ircFormat(text, from, color, key, datetime) { // Generate and apply color to uncolored names, if (!color && from) @@ -912,8 +923,18 @@ function ircFormat(text, from, color, key) // Build time header if enabled if (g_timestamp) { - // Time for optional time header - var time = new Date(Date.now()); + + var time; + if (datetime) + { + var parserDate = datetime.split("T")[0].split("-"); + var parserTime = datetime.split("T")[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: diff --git a/source/lobby/XmppClient.cpp b/source/lobby/XmppClient.cpp index 0bc1a0e9db..b6b3dbb0e5 100644 --- a/source/lobby/XmppClient.cpp +++ b/source/lobby/XmppClient.cpp @@ -615,7 +615,9 @@ void XmppClient::GuiPollMessage(ScriptInterface& scriptInterface, JS::MutableHan scriptInterface.SetProperty(ret, "message", message.message); if (!message.data.empty()) scriptInterface.SetProperty(ret, "data", message.data); - + if (!message.datetime.empty()) + scriptInterface.SetProperty(ret, "datetime", message.datetime); + m_GuiMessageQueue.pop_front(); } @@ -648,6 +650,9 @@ void XmppClient::handleMUCMessage(glooxwrapper::MUCRoom*, const glooxwrapper::Me message.type = L"mucmessage"; 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(); PushGuiMessage(message); } @@ -662,6 +667,9 @@ void XmppClient::handleMessage(const glooxwrapper::Message& msg, glooxwrapper::M GUIMessage message; message.from = wstring_from_utf8(msg.from().username().to_string()); message.message = 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(); PushGuiMessage(message); } diff --git a/source/lobby/XmppClient.h b/source/lobby/XmppClient.h index 81d043b39c..02b64f5635 100644 --- a/source/lobby/XmppClient.h +++ b/source/lobby/XmppClient.h @@ -131,6 +131,7 @@ public: std::wstring data; std::wstring from; std::wstring message; + std::string datetime; }; void GuiPollMessage(ScriptInterface& scriptInterface, JS::MutableHandleValue ret); void SendMUCMessage(const std::string& message); diff --git a/source/lobby/glooxwrapper/glooxwrapper.cpp b/source/lobby/glooxwrapper/glooxwrapper.cpp index b624e521bc..64dd4db34b 100644 --- a/source/lobby/glooxwrapper/glooxwrapper.cpp +++ b/source/lobby/glooxwrapper/glooxwrapper.cpp @@ -383,6 +383,17 @@ void glooxwrapper::Client::setPresence(gloox::Presence::PresenceType pres, int p } +glooxwrapper::DelayedDelivery::DelayedDelivery(const gloox::DelayedDelivery* wrapped) +{ + m_Wrapped = wrapped; +} + +const glooxwrapper::string glooxwrapper::DelayedDelivery::stamp() const +{ + return m_Wrapped->stamp(); +} + + glooxwrapper::Disco::Disco(gloox::Disco* wrapped) { m_Wrapped = wrapped; @@ -513,6 +524,14 @@ glooxwrapper::string glooxwrapper::Message::thread() const return m_Wrapped->thread(); } +const glooxwrapper::DelayedDelivery* glooxwrapper::Message::when() const +{ + const gloox::DelayedDelivery* wrapped = m_Wrapped->when(); + if (wrapped == 0) + return 0; + return new glooxwrapper::DelayedDelivery(wrapped); +} + glooxwrapper::MUCRoom::MUCRoom(Client* parent, const JID& nick, MUCRoomHandler* mrh, MUCRoomConfigHandler* UNUSED(mrch)) { diff --git a/source/lobby/glooxwrapper/glooxwrapper.h b/source/lobby/glooxwrapper/glooxwrapper.h index 0e8d7138c3..116423c8a4 100644 --- a/source/lobby/glooxwrapper/glooxwrapper.h +++ b/source/lobby/glooxwrapper/glooxwrapper.h @@ -85,6 +85,7 @@ namespace glooxwrapper { class Client; class DataForm; + class DelayedDelivery; class Disco; class IQ; class JID; @@ -406,6 +407,16 @@ namespace glooxwrapper void disconnect(); }; + class GLOOXWRAPPER_API DelayedDelivery + { + NONCOPYABLE(DelayedDelivery); + private: + const gloox::DelayedDelivery* m_Wrapped; + public: + DelayedDelivery(const gloox::DelayedDelivery* wrapped); + const string stamp() const; + }; + class GLOOXWRAPPER_API Disco { NONCOPYABLE(Disco); @@ -480,6 +491,7 @@ namespace glooxwrapper string body() const; string subject(const string& lang = "default") const; string thread() const; + const glooxwrapper::DelayedDelivery* when() const; }; class GLOOXWRAPPER_API MUCRoom