Index: binaries/data/mods/public/gui/lobby/LobbyPage/Chat/ChatCommandHandler.js =================================================================== --- binaries/data/mods/public/gui/lobby/LobbyPage/Chat/ChatCommandHandler.js +++ binaries/data/mods/public/gui/lobby/LobbyPage/Chat/ChatCommandHandler.js @@ -97,6 +97,20 @@ return true; } }, + "private": { + "description": translate("Send private message to user. Usage: /private nick message"), + "handler": function(args) { + let index = args.indexOf(" "); + if (index != -1) + { + let target = args.substr(0, index); + let msg = args.substr(index + 1); + Engine.PrivateMessage(target, msg); + return true; + } + return false; + } + }, "kick": { "description": translate("Kick a specified user from the lobby. Usage: /kick nick reason"), "handler": function(args) { Index: binaries/data/mods/public/gui/lobby/LobbyPage/Chat/ChatMessages/ChatMessageEvents.js =================================================================== --- binaries/data/mods/public/gui/lobby/LobbyPage/Chat/ChatMessages/ChatMessageEvents.js +++ binaries/data/mods/public/gui/lobby/LobbyPage/Chat/ChatMessages/ChatMessageEvents.js @@ -9,6 +9,7 @@ this.chatMessageFormat = new ChatMessageFormat(); xmppMessages.registerXmppMessageHandler("chat", "room-message", this.onRoomMessage.bind(this)); xmppMessages.registerXmppMessageHandler("chat", "private-message", this.onPrivateMessage.bind(this)); + xmppMessages.registerXmppMessageHandler("chat", "private-send-message", this.onPrivateSendMessage.bind(this)); } onRoomMessage(message) @@ -19,8 +20,13 @@ onPrivateMessage(message) { // We intend to not support private messages between users - if ((!message.from && message.text.length > 0) || Engine.LobbyGetPlayerRole(message.from) == "moderator") + if (message.text.length > 0) // some XMPP clients send trailing whitespace this.chatMessagesPanel.addText(message.time, this.chatMessageFormat.format(message)); } + + onPrivateSendMessage(message) + { + this.chatMessagesPanel.addText(message.time, this.chatMessageFormat.format(message)); + } }; Index: binaries/data/mods/public/gui/lobby/LobbyPage/Chat/ChatMessages/ChatMessageFormat.js =================================================================== --- binaries/data/mods/public/gui/lobby/LobbyPage/Chat/ChatMessages/ChatMessageFormat.js +++ binaries/data/mods/public/gui/lobby/LobbyPage/Chat/ChatMessages/ChatMessageFormat.js @@ -11,6 +11,7 @@ this.chatMessageFormatMe = new ChatMessageFormatMe(); this.chatMessageFormatSay = new ChatMessageFormatSay(); this.chatMessagePrivateWrapper = new ChatMessagePrivateWrapper(); + this.chatMessagePrivateSenderFormat = new PrivateSenderFormat(); } /** @@ -53,16 +54,23 @@ } default: { - formattedMessage = this.chatMessageFormatSay.format(sender, text); + if (message.level == "private-send-message") + formattedMessage = text; + else + formattedMessage = this.chatMessageFormatSay.format(sender, text); break; } } } + else if (message.level == "private-send-message") + formattedMessage = text; else formattedMessage = this.chatMessageFormatSay.format(sender, text); if (message.level == "private-message") formattedMessage = this.chatMessagePrivateWrapper.format(formattedMessage); + if (message.level == "private-send-message") + formattedMessage = this.chatMessagePrivateSenderFormat.format(g_Nickname, message.to, formattedMessage); return formattedMessage; } Index: binaries/data/mods/public/gui/lobby/LobbyPage/Chat/ChatMessages/PrivateSenderFormat.js =================================================================== --- /dev/null +++ binaries/data/mods/public/gui/lobby/LobbyPage/Chat/ChatMessages/PrivateSenderFormat.js @@ -0,0 +1,39 @@ +/** + * This class formats sending private message. + */ +class PrivateSenderFormat +{ + constructor() + { + this.args = { + "private": setStringTags(this.PrivateFormat, this.PrivateMessageTags) + }; + } + + /** + * Text is formatted, escapeText is the responsibility of the caller. + */ + format(sender, to, text) + { + this.args.message = text; + this.args.sender = setStringTags( sprintf(this.SenderFormat, {"sender": PlayerColor.ColorPlayerName(sender, undefined, Engine.LobbyGetPlayerRole(sender))}), this.SenderTags); + this.args.to = PlayerColor.ColorPlayerName(to, undefined, Engine.LobbyGetPlayerRole(to)); + return sprintf(this.PrivateMessageFormat, this.args); + } +} + +PrivateSenderFormat.prototype.PrivateFormat = translate("Private"); + +PrivateSenderFormat.prototype.PrivateMessageFormat = translate("%(sender)s (%(private)s<%(to)s>) %(message)s"); + +PrivateSenderFormat.prototype.SenderFormat = "<%(sender)s>"; + +/** + * Color for private messages in the chat. + */ +PrivateSenderFormat.prototype.PrivateMessageTags = { + "color": "0 150 0" +}; +PrivateSenderFormat.prototype.SenderTags = { + "font": "sans-bold-13" +}; Index: binaries/data/mods/public/gui/lobby/XmppMessages.js =================================================================== --- binaries/data/mods/public/gui/lobby/XmppMessages.js +++ binaries/data/mods/public/gui/lobby/XmppMessages.js @@ -104,7 +104,8 @@ "kicked", "banned", "room-message", - "private-message" + "private-message", + "private-send-message" ], "game": [ "gamelist", Index: source/lobby/IXmppClient.h =================================================================== --- source/lobby/IXmppClient.h +++ source/lobby/IXmppClient.h @@ -54,7 +54,7 @@ virtual JS::Value GUIGetGameList(const ScriptRequest& rq) = 0; virtual JS::Value GUIGetBoardList(const ScriptRequest& rq) = 0; virtual JS::Value GUIGetProfile(const ScriptRequest& rq) = 0; - + virtual void SendPrivateMessage(const std::string& username, const std::string& msg) = 0; virtual JS::Value GuiPollNewMessages(const ScriptInterface& guiInterface) = 0; virtual JS::Value GuiPollHistoricMessages(const ScriptInterface& guiInterface) = 0; virtual bool GuiPollHasPlayerListUpdate() = 0; Index: source/lobby/XmppClient.h =================================================================== --- source/lobby/XmppClient.h +++ source/lobby/XmppClient.h @@ -161,6 +161,7 @@ public: JS::Value GuiPollNewMessages(const ScriptInterface& guiInterface); JS::Value GuiPollHistoricMessages(const ScriptInterface& guiInterface); + void SendPrivateMessage(const std::string& username, const std::string& msg); bool GuiPollHasPlayerListUpdate(); void SendMUCMessage(const std::string& message); Index: source/lobby/XmppClient.cpp =================================================================== --- source/lobby/XmppClient.cpp +++ source/lobby/XmppClient.cpp @@ -723,6 +723,34 @@ return hasUpdate; } +void XmppClient::SendPrivateMessage(const std::string& username, const std::string& msg) +{ + if ((m_isConnected && !m_initialLoadComplete)) + return; + + glooxwrapper::JID targetJID(m_room + "@conference." + m_server + "/" + username); + glooxwrapper::MessageSession session = glooxwrapper::MessageSession(m_client, targetJID); + session.send(msg); + m_client->disposeMessageSession(session); + const std::string emptyString; + gloox::Message glmsg( + gloox::Message::MessageType::Normal, + targetJID.getWrapped(), + msg, + emptyString, + emptyString, + emptyString + ); + glooxwrapper::Message gmsg(&glmsg, true); + CreateGUIMessage( + "chat", + "private-send-message", + ComputeTimestamp(gmsg), + "from", m_nick, + "to", glmsg.to().resource(), + "text", gmsg.body()); +} + JS::Value XmppClient::GuiPollNewMessages(const ScriptInterface& guiInterface) { if ((m_isConnected && !m_initialLoadComplete) || m_GuiMessageQueue.empty()) Index: source/lobby/glooxwrapper/glooxwrapper.h =================================================================== --- source/lobby/glooxwrapper/glooxwrapper.h +++ source/lobby/glooxwrapper/glooxwrapper.h @@ -72,6 +72,7 @@ #include #include #include +#include #include #include #include @@ -338,7 +339,6 @@ JID* alternate; }; - class GLOOXWRAPPER_API ConnectionListener { public: @@ -399,7 +399,6 @@ int m_extensionType; }; - class GLOOXWRAPPER_API Client { NONCOPYABLE(Client); @@ -436,6 +435,8 @@ void setPresence(gloox::Presence::PresenceType pres, int priority, const string& status = ""); void disconnect(); + + void disposeMessageSession(MessageSession&); }; class GLOOXWRAPPER_API DelayedDelivery @@ -528,6 +529,19 @@ const glooxwrapper::DelayedDelivery* when() const; }; + class GLOOXWRAPPER_API MessageSession + { + NONCOPYABLE(MessageSession); + private: + gloox::MessageSession* m_Wrapped; + + public: + gloox::MessageSession* getWrapped() { return m_Wrapped; } + MessageSession(Client* parent, const JID& jid); + ~MessageSession(); + void send(const std::string& message); + }; + class GLOOXWRAPPER_API MUCRoom { NONCOPYABLE(MUCRoom); Index: source/lobby/glooxwrapper/glooxwrapper.cpp =================================================================== --- source/lobby/glooxwrapper/glooxwrapper.cpp +++ source/lobby/glooxwrapper/glooxwrapper.cpp @@ -364,6 +364,11 @@ m_Wrapped->send(iq.getWrapped()); } +void glooxwrapper::Client::disposeMessageSession(glooxwrapper::MessageSession& messageSession) +{ + m_Wrapped->disposeMessageSession(messageSession.getWrapped()); +} + void glooxwrapper::Client::setTls(gloox::TLSPolicy tls) { m_Wrapped->setTls(tls); @@ -602,6 +607,22 @@ delete m_HandlerWrapper; } +glooxwrapper::MessageSession::MessageSession(Client* parent, const JID& jid) +{ + m_Wrapped = new gloox::MessageSession(parent->getWrapped(), jid.getWrapped(), false, 0, false); +} + +glooxwrapper::MessageSession::~MessageSession() +{ + // Do not delete gloox::MessageSession manually + // ClientBase::disposeMessageSession() will handle that +} + +void glooxwrapper::MessageSession::send(const std::string& message) +{ + m_Wrapped->send(message); +} + const glooxwrapper::string glooxwrapper::MUCRoom::nick() const { return m_Wrapped->nick(); Index: source/lobby/scripting/JSInterface_Lobby.cpp =================================================================== --- source/lobby/scripting/JSInterface_Lobby.cpp +++ source/lobby/scripting/JSInterface_Lobby.cpp @@ -138,6 +138,13 @@ return g_XmppClient->GuiPollNewMessages(scriptInterface); } +void PrivateMessage(const std::string& username, const std::string& msg) +{ + if (!g_XmppClient) + return; + + g_XmppClient->SendPrivateMessage(username, msg); +} // Non-public secure PBKDF2 hash function with salting and 1,337 iterations // @@ -219,6 +226,7 @@ REGISTER_XMPP(GetNick, "LobbyGetNick"); REGISTER_XMPP(GetJID, "LobbyGetJID"); REGISTER_XMPP(kick, "LobbyKick"); + ScriptFunction::Register<&PrivateMessage>(rq, "PrivateMessage"); REGISTER_XMPP(ban, "LobbyBan"); REGISTER_XMPP(GetPresence, "LobbyGetPlayerPresence"); REGISTER_XMPP(GetRole, "LobbyGetPlayerRole");