1 /* Copyright (C) 2017 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
18 #include "precompiled.h"
19 #include "XmppClient.h"
20 #include "StanzaExtensions.h"
23 # include <winsock2.h>
26 #include "i18n/L10n.h"
28 #include "lib/external_libraries/enet.h"
30 #include "network/NetServer.h"
31 #include "network/StunClient.h"
32 #include "ps/CLogger.h"
33 #include "ps/ConfigDB.h"
34 #include "ps/Pyrogenesis.h"
35 #include "scriptinterface/ScriptInterface.h"
41 #define DbgXMPP(x) std::cout << x << std::endl;
43 static std::string
tag_xml(const glooxwrapper::IQ
& iq
)
46 glooxwrapper::Tag
* tag
= iq
.tag();
47 ret
= tag
->xml().to_string();
48 glooxwrapper::Tag::free(tag
);
53 static std::string
tag_name(const glooxwrapper::IQ
& iq
)
56 glooxwrapper::Tag
* tag
= iq
.tag();
57 ret
= tag
->name().to_string();
58 glooxwrapper::Tag::free(tag
);
62 IXmppClient
* IXmppClient::create(const std::string
& sUsername
, const std::string
& sPassword
, const std::string
& sRoom
, const std::string
& sNick
, const int historyRequestSize
,bool regOpt
)
64 return new XmppClient(sUsername
, sPassword
, sRoom
, sNick
, historyRequestSize
, regOpt
);
68 * Construct the XMPP client.
70 * @param sUsername Username to login with of register.
71 * @param sPassword Password to login with or register.
72 * @param sRoom MUC room to join.
73 * @param sNick Nick to join with.
74 * @param historyRequestSize Number of stanzas of room history to request.
75 * @param regOpt If we are just registering or not.
77 XmppClient::XmppClient(const std::string
& sUsername
, const std::string
& sPassword
, const std::string
& sRoom
, const std::string
& sNick
, const int historyRequestSize
, bool regOpt
)
78 : m_client(NULL
), m_mucRoom(NULL
), m_registration(NULL
), m_username(sUsername
), m_password(sPassword
), m_nick(sNick
), m_initialLoadComplete(false), m_sessionManager()
80 // Read lobby configuration from default.cfg
82 std::string sXpartamupp
;
83 CFG_GET_VAL("lobby.server", sServer
);
84 CFG_GET_VAL("lobby.xpartamupp", sXpartamupp
);
86 m_xpartamuppId
= sXpartamupp
+ "@" + sServer
+ "/CC";
87 glooxwrapper::JID
clientJid(sUsername
+ "@" + sServer
+ "/0ad");
88 glooxwrapper::JID
roomJid(sRoom
+ "@conference." + sServer
+ "/" + sNick
);
90 // If we are connecting, use the full jid and a password
91 // If we are registering, only use the server name
93 m_client
= new glooxwrapper::Client(clientJid
, sPassword
);
95 m_client
= new glooxwrapper::Client(sServer
);
97 // Disable TLS as we haven't set a certificate on the server yet
98 m_client
->setTls(gloox::TLSDisabled
);
100 // Disable use of the SASL PLAIN mechanism, to prevent leaking credentials
101 // if the server doesn't list any supported SASL mechanism or the response
102 // has been modified to exclude those.
103 const int mechs
= gloox::SaslMechAll
^ gloox::SaslMechPlain
;
104 m_client
->setSASLMechanisms(mechs
);
106 m_client
->registerConnectionListener(this);
107 m_client
->setPresence(gloox::Presence::Available
, -1);
108 m_client
->disco()->setVersion("Pyrogenesis", engine_version
);
109 m_client
->disco()->setIdentity("client", "bot");
110 m_client
->setCompression(false);
112 m_client
->registerStanzaExtension(new GameListQuery());
113 m_client
->registerIqHandler(this, EXTGAMELISTQUERY
);
115 m_client
->registerStanzaExtension(new BoardListQuery());
116 m_client
->registerIqHandler(this, EXTBOARDLISTQUERY
);
118 m_client
->registerStanzaExtension(new ProfileQuery());
119 m_client
->registerIqHandler(this, EXTPROFILEQUERY
);
121 m_client
->registerMessageHandler(this);
123 // Uncomment to see the raw stanzas
124 //m_client->getWrapped()->logInstance().registerLogHandler( gloox::LogLevelDebug, gloox::LogAreaAll, this );
128 // Create a Multi User Chat Room
129 m_mucRoom
= new glooxwrapper::MUCRoom(m_client
, roomJid
, this, 0);
131 m_mucRoom
->setRequestHistory(historyRequestSize
, gloox::MUCRoom::HistoryMaxStanzas
);
136 m_registration
= new glooxwrapper::Registration(m_client
);
137 m_registration
->registerRegistrationHandler(this);
140 m_sessionManager
= new glooxwrapper::SessionManager(m_client
, this);
141 // Register plugins to allow gloox parse them in incoming sessions
142 m_sessionManager
->registerPlugins();
146 * Destroy the xmpp client
148 XmppClient::~XmppClient()
150 DbgXMPP("XmppClient destroyed");
151 delete m_registration
;
154 // Workaround for memory leak in gloox 1.0/1.0.1
155 m_client
->removePresenceExtension(gloox::ExtCaps
);
159 for (const glooxwrapper::Tag
* const& t
: m_GameList
)
160 glooxwrapper::Tag::free(t
);
161 for (const glooxwrapper::Tag
* const& t
: m_BoardList
)
162 glooxwrapper::Tag::free(t
);
163 for (const glooxwrapper::Tag
* const& t
: m_Profile
)
164 glooxwrapper::Tag::free(t
);
168 void XmppClient::connect()
170 m_initialLoadComplete
= false;
171 m_client
->connect(false);
174 void XmppClient::disconnect()
176 m_client
->disconnect();
179 void XmppClient::recv()
185 * Log (debug) Handler
187 void XmppClient::handleLog(gloox::LogLevel level
, gloox::LogArea area
, const std::string
& message
)
189 std::cout
<< "log: level: " << level
<< ", area: " << area
<< ", message: " << message
<< std::endl
;
192 /*****************************************************
193 * Connection handlers *
194 *****************************************************/
199 void XmppClient::onConnect()
203 CreateGUIMessage("system", "connected");
208 m_registration
->fetchRegistrationFields();
212 * Handle disconnection
214 void XmppClient::onDisconnect(gloox::ConnectionError error
)
216 // Make sure we properly leave the room so that
217 // everything works if we decide to come back later
221 // Clear game, board and player lists.
222 for (const glooxwrapper::Tag
* const& t
: m_GameList
)
223 glooxwrapper::Tag::free(t
);
224 for (const glooxwrapper::Tag
* const& t
: m_BoardList
)
225 glooxwrapper::Tag::free(t
);
226 for (const glooxwrapper::Tag
* const& t
: m_Profile
)
227 glooxwrapper::Tag::free(t
);
232 m_HistoricGuiMessages
.clear();
234 CreateGUIMessage("system", "disconnected", "reason", ConnectionErrorToString(error
));
238 * Handle TLS connection
240 bool XmppClient::onTLSConnect(const glooxwrapper::CertInfo
& info
)
243 DbgXMPP("onTLSConnect");
245 "status: " << info
.status
<<
246 "\nissuer: " << info
.issuer
<<
247 "\npeer: " << info
.server
<<
248 "\nprotocol: " << info
.protocol
<<
249 "\nmac: " << info
.mac
<<
250 "\ncipher: " << info
.cipher
<<
251 "\ncompression: " << info
.compression
);
256 * Handle MUC room errors
258 void XmppClient::handleMUCError(glooxwrapper::MUCRoom
*, gloox::StanzaError err
)
260 CreateGUIMessage("system", "error", "text", StanzaErrorToString(err
));
263 /*****************************************************
264 * Requests to server *
265 *****************************************************/
268 * Request the leaderboard data from the server.
270 void XmppClient::SendIqGetBoardList()
272 glooxwrapper::JID
xpartamuppJid(m_xpartamuppId
);
275 BoardListQuery
* b
= new BoardListQuery();
276 b
->m_Command
= "getleaderboard";
277 glooxwrapper::IQ
iq(gloox::IQ::Get
, xpartamuppJid
);
279 DbgXMPP("SendIqGetBoardList [" << tag_xml(iq
) << "]");
284 * Request the profile data from the server.
286 void XmppClient::SendIqGetProfile(const std::string
& player
)
288 glooxwrapper::JID
xpartamuppJid(m_xpartamuppId
);
291 ProfileQuery
* b
= new ProfileQuery();
292 b
->m_Command
= player
;
293 glooxwrapper::IQ
iq(gloox::IQ::Get
, xpartamuppJid
);
295 DbgXMPP("SendIqGetProfile [" << tag_xml(iq
) << "]");
300 * Send game report containing numerous game properties to the server.
302 * @param data A JS array of game statistics
304 void XmppClient::SendIqGameReport(const ScriptInterface
& scriptInterface
, JS::HandleValue data
)
306 glooxwrapper::JID
xpartamuppJid(m_xpartamuppId
);
308 // Setup some base stanza attributes
309 GameReport
* game
= new GameReport();
310 glooxwrapper::Tag
* report
= glooxwrapper::Tag::allocate("game");
312 // Iterate through all the properties reported and add them to the stanza.
313 std::vector
<std::string
> properties
;
314 scriptInterface
.EnumeratePropertyNamesWithPrefix(data
, "", properties
);
315 for (const std::string
& p
: properties
)
318 scriptInterface
.GetProperty(data
, p
.c_str(), value
);
319 report
->addAttribute(p
, utf8_from_wstring(value
));
323 game
->m_GameReport
.emplace_back(report
);
326 glooxwrapper::IQ
iq(gloox::IQ::Set
, xpartamuppJid
);
327 iq
.addExtension(game
);
328 DbgXMPP("SendGameReport [" << tag_xml(iq
) << "]");
333 * Send a request to register a game to the server.
335 * @param data A JS array of game attributes
337 void XmppClient::SendIqRegisterGame(const ScriptInterface
& scriptInterface
, JS::HandleValue data
)
339 glooxwrapper::JID
xpartamuppJid(m_xpartamuppId
);
341 // Setup some base stanza attributes
342 GameListQuery
* g
= new GameListQuery();
343 g
->m_Command
= "register";
344 glooxwrapper::Tag
* game
= glooxwrapper::Tag::allocate("game");
345 // Add a fake ip which will be overwritten by the ip stamp XMPP module on the server.
346 game
->addAttribute("ip", "fake");
348 // Iterate through all the properties reported and add them to the stanza.
349 std::vector
<std::string
> properties
;
350 scriptInterface
.EnumeratePropertyNamesWithPrefix(data
, "", properties
);
351 for (const std::string
& p
: properties
)
354 scriptInterface
.GetProperty(data
, p
.c_str(), value
);
355 game
->addAttribute(p
, utf8_from_wstring(value
));
358 // Push the stanza onto the IQ
359 g
->m_GameList
.emplace_back(game
);
362 glooxwrapper::IQ
iq(gloox::IQ::Set
, xpartamuppJid
);
364 DbgXMPP("SendIqRegisterGame [" << tag_xml(iq
) << "]");
369 * Send a request to unregister a game to the server.
371 void XmppClient::SendIqUnregisterGame()
373 glooxwrapper::JID
xpartamuppJid(m_xpartamuppId
);
376 GameListQuery
* g
= new GameListQuery();
377 g
->m_Command
= "unregister";
378 g
->m_GameList
.emplace_back(glooxwrapper::Tag::allocate("game"));
380 glooxwrapper::IQ
iq( gloox::IQ::Set
, xpartamuppJid
);
382 DbgXMPP("SendIqUnregisterGame [" << tag_xml(iq
) << "]");
387 * Send a request to change the state of a registered game on the server.
389 * A game can either be in the 'running' or 'waiting' state - the server
390 * decides which - but we need to update the current players that are
391 * in-game so the server can make the calculation.
393 void XmppClient::SendIqChangeStateGame(const std::string
& nbp
, const std::string
& players
)
395 glooxwrapper::JID
xpartamuppJid(m_xpartamuppId
);
398 GameListQuery
* g
= new GameListQuery();
399 g
->m_Command
= "changestate";
400 glooxwrapper::Tag
* game
= glooxwrapper::Tag::allocate("game");
401 game
->addAttribute("nbp", nbp
);
402 game
->addAttribute("players", players
);
403 g
->m_GameList
.emplace_back(game
);
405 glooxwrapper::IQ
iq(gloox::IQ::Set
, xpartamuppJid
);
407 DbgXMPP("SendIqChangeStateGame [" << tag_xml(iq
) << "]");
411 /*****************************************************
412 * Account registration *
413 *****************************************************/
415 void XmppClient::handleRegistrationFields(const glooxwrapper::JID
&, int fields
, glooxwrapper::string
)
417 glooxwrapper::RegistrationFields vals
;
418 vals
.username
= m_username
;
419 vals
.password
= m_password
;
420 m_registration
->createAccount(fields
, vals
);
423 void XmppClient::handleRegistrationResult(const glooxwrapper::JID
&, gloox::RegistrationResult result
)
425 if (result
== gloox::RegistrationSuccess
)
426 CreateGUIMessage("system", "registered");
428 CreateGUIMessage("system", "error", "text", RegistrationResultToString(result
));
433 void XmppClient::handleAlreadyRegistered(const glooxwrapper::JID
&)
435 DbgXMPP("the account already exists");
438 void XmppClient::handleDataForm(const glooxwrapper::JID
&, const glooxwrapper::DataForm
&)
440 DbgXMPP("dataForm received");
443 void XmppClient::handleOOB(const glooxwrapper::JID
&, const glooxwrapper::OOB
&)
445 DbgXMPP("OOB registration requested");
448 /*****************************************************
449 * Requests from GUI *
450 *****************************************************/
453 * Handle requests from the GUI for the list of players.
455 * @return A JS array containing all known players and their presences
457 void XmppClient::GUIGetPlayerList(const ScriptInterface
& scriptInterface
, JS::MutableHandleValue ret
)
459 JSContext
* cx
= scriptInterface
.GetContext();
460 JSAutoRequest
rq(cx
);
462 scriptInterface
.Eval("([])", ret
);
464 // Convert the internal data structure to a Javascript object.
465 for (const std::pair
<std::string
, std::vector
<std::string
> >& p
: m_PlayerMap
)
467 JS::RootedValue
player(cx
);
468 scriptInterface
.Eval("({})", &player
);
469 scriptInterface
.SetProperty(player
, "name", wstring_from_utf8(p
.first
));
470 scriptInterface
.SetProperty(player
, "presence", wstring_from_utf8(p
.second
[0]));
471 scriptInterface
.SetProperty(player
, "rating", wstring_from_utf8(p
.second
[1]));
472 scriptInterface
.SetProperty(player
, "role", wstring_from_utf8(p
.second
[2]));
473 scriptInterface
.CallFunctionVoid(ret
, "push", player
);
478 * Handle requests from the GUI for the list of all active games.
480 * @return A JS array containing all known games
482 void XmppClient::GUIGetGameList(const ScriptInterface
& scriptInterface
, JS::MutableHandleValue ret
)
484 JSContext
* cx
= scriptInterface
.GetContext();
485 JSAutoRequest
rq(cx
);
487 scriptInterface
.Eval("([])", ret
);
488 const char* stats
[] = { "name", "ip", "port", "stunIP", "stunPort", "hostUsername", "state", "nbp", "maxnbp", "players", "mapName", "niceMapName", "mapSize", "mapType", "victoryCondition", "startTime" };
489 for(const glooxwrapper::Tag
* const& t
: m_GameList
)
491 JS::RootedValue
game(cx
);
492 scriptInterface
.Eval("({})", &game
);
494 for (size_t i
= 0; i
< ARRAY_SIZE(stats
); ++i
)
495 scriptInterface
.SetProperty(game
, stats
[i
], wstring_from_utf8(t
->findAttribute(stats
[i
]).to_string()));
497 scriptInterface
.CallFunctionVoid(ret
, "push", game
);
502 * Handle requests from the GUI for leaderboard data.
504 * @return A JS array containing all known leaderboard data
506 void XmppClient::GUIGetBoardList(const ScriptInterface
& scriptInterface
, JS::MutableHandleValue ret
)
508 JSContext
* cx
= scriptInterface
.GetContext();
509 JSAutoRequest
rq(cx
);
511 scriptInterface
.Eval("([])", ret
);
512 const char* attributes
[] = { "name", "rank", "rating" };
513 for(const glooxwrapper::Tag
* const& t
: m_BoardList
)
515 JS::RootedValue
board(cx
);
516 scriptInterface
.Eval("({})", &board
);
518 for (size_t i
= 0; i
< ARRAY_SIZE(attributes
); ++i
)
519 scriptInterface
.SetProperty(board
, attributes
[i
], wstring_from_utf8(t
->findAttribute(attributes
[i
]).to_string()));
521 scriptInterface
.CallFunctionVoid(ret
, "push", board
);
526 * Handle requests from the GUI for profile data.
528 * @return A JS array containing the specific user's profile data
530 void XmppClient::GUIGetProfile(const ScriptInterface
& scriptInterface
, JS::MutableHandleValue ret
)
532 JSContext
* cx
= scriptInterface
.GetContext();
533 JSAutoRequest
rq(cx
);
535 scriptInterface
.Eval("([])", ret
);
536 const char* stats
[] = { "player", "rating", "totalGamesPlayed", "highestRating", "wins", "losses", "rank" };
537 for (const glooxwrapper::Tag
* const& t
: m_Profile
)
539 JS::RootedValue
profile(cx
);
540 scriptInterface
.Eval("({})", &profile
);
542 for (size_t i
= 0; i
< ARRAY_SIZE(stats
); ++i
)
543 scriptInterface
.SetProperty(profile
, stats
[i
], wstring_from_utf8(t
->findAttribute(stats
[i
]).to_string()));
545 scriptInterface
.CallFunctionVoid(ret
, "push", profile
);
549 /*****************************************************
550 * Message interfaces *
551 *****************************************************/
553 void XmppClient::CreateGUIMessage(
554 const std::string
& type
,
555 const std::string
& level
,
556 const std::string
& property1_name
,
557 const std::string
& property1_value
,
558 const std::string
& property2_name
,
559 const std::string
& property2_value
,
560 const std::time_t time
)
564 message
.level
= level
;
565 message
.property1_name
= property1_name
;
566 message
.property1_value
= property1_value
;
567 message
.property2_name
= property2_name
;
568 message
.property2_value
= property2_value
;
570 m_GuiMessageQueue
.push_back(std::move(message
));
573 JS::Value
XmppClient::GuiMessageToJSVal(const ScriptInterface
& scriptInterface
, const GUIMessage
& message
, const bool historic
)
575 JSContext
* cx
= scriptInterface
.GetContext();
576 JSAutoRequest
rq(cx
);
577 JS::RootedValue
ret(cx
);
578 scriptInterface
.Eval("({})", &ret
);
579 scriptInterface
.SetProperty(ret
, "type", wstring_from_utf8(message
.type
));
580 if (!message
.level
.empty())
581 scriptInterface
.SetProperty(ret
, "level", wstring_from_utf8(message
.level
));
582 if (!message
.property1_name
.empty())
583 scriptInterface
.SetProperty(ret
, message
.property1_name
.c_str(), wstring_from_utf8(message
.property1_value
));
584 if (!message
.property2_name
.empty())
585 scriptInterface
.SetProperty(ret
, message
.property2_name
.c_str(), wstring_from_utf8(message
.property2_value
));
586 scriptInterface
.SetProperty(ret
, "time", (double)message
.time
);
587 scriptInterface
.SetProperty(ret
, "historic", historic
);
591 JS::Value
XmppClient::GuiPollNewMessage(const ScriptInterface
& scriptInterface
)
593 if (m_GuiMessageQueue
.empty())
594 return JS::UndefinedValue();
596 GUIMessage message
= m_GuiMessageQueue
.front();
597 m_GuiMessageQueue
.pop_front();
599 // Since there can be hundreds of presence changes while playing a game, ignore these for performance
600 if (message
.type
== "chat" && message
.level
!= "presence")
601 m_HistoricGuiMessages
.push_back(message
);
603 return GuiMessageToJSVal(scriptInterface
, message
, false);
606 JS::Value
XmppClient::GuiPollHistoricMessages(const ScriptInterface
& scriptInterface
)
608 JSContext
* cx
= scriptInterface
.GetContext();
609 JSAutoRequest
rq(cx
);
610 JS::RootedObject
ret(cx
, JS_NewArrayObject(cx
, 0));
613 for (const GUIMessage
& message
: m_HistoricGuiMessages
)
615 JS::RootedValue
msg(cx
, GuiMessageToJSVal(scriptInterface
, message
, true));
616 JS_SetElement(cx
, ret
, i
++, msg
);
618 return JS::ObjectValue(*ret
);
622 * Send a standard MUC textual message.
624 void XmppClient::SendMUCMessage(const std::string
& message
)
626 m_mucRoom
->send(message
);
630 * Clears all presence updates from the message queue.
631 * Used when rejoining the lobby, since we don't need to handle past presence changes.
633 void XmppClient::ClearPresenceUpdates()
635 m_GuiMessageQueue
.erase(
636 std::remove_if(m_GuiMessageQueue
.begin(), m_GuiMessageQueue
.end(),
637 [](XmppClient::GUIMessage
& message
)
639 return message
.type
== "chat" && message
.level
== "presence";
641 ), m_GuiMessageQueue
.end());
645 * Handle a room message.
647 void XmppClient::handleMUCMessage(glooxwrapper::MUCRoom
*, const glooxwrapper::Message
& msg
, bool priv
)
649 DbgXMPP(msg
.from().resource() << " said " << msg
.body());
653 priv
? "private-message" : "room-message",
654 "from", msg
.from().resource().to_string(),
655 "text", msg
.body().to_string(),
656 ComputeTimestamp(msg
));
660 * Handle a private message.
662 void XmppClient::handleMessage(const glooxwrapper::Message
& msg
, glooxwrapper::MessageSession
*)
664 DbgXMPP("type " << msg
.subtype() << ", subject " << msg
.subject()
665 << ", message " << msg
.body() << ", thread id " << msg
.thread());
670 "from", msg
.from().resource().to_string(),
671 "text", msg
.body().to_string(),
672 ComputeTimestamp(msg
));
676 * Handle portions of messages containing custom stanza extensions.
678 bool XmppClient::handleIq(const glooxwrapper::IQ
& iq
)
680 DbgXMPP("handleIq [" << tag_xml(iq
) << "]");
682 if (iq
.subtype() == gloox::IQ::Result
)
684 const GameListQuery
* gq
= iq
.findExtension
<GameListQuery
>(EXTGAMELISTQUERY
);
685 const BoardListQuery
* bq
= iq
.findExtension
<BoardListQuery
>(EXTBOARDLISTQUERY
);
686 const ProfileQuery
* pq
= iq
.findExtension
<ProfileQuery
>(EXTPROFILEQUERY
);
689 for (const glooxwrapper::Tag
* const& t
: m_GameList
)
690 glooxwrapper::Tag::free(t
);
693 for (const glooxwrapper::Tag
* const& t
: gq
->m_GameList
)
694 m_GameList
.emplace_back(t
->clone());
696 CreateGUIMessage("game", "gamelist");
700 if (bq
->m_Command
== "boardlist")
702 for (const glooxwrapper::Tag
* const& t
: m_BoardList
)
703 glooxwrapper::Tag::free(t
);
706 for (const glooxwrapper::Tag
* const& t
: bq
->m_StanzaBoardList
)
707 m_BoardList
.emplace_back(t
->clone());
709 CreateGUIMessage("game", "leaderboard");
711 else if (bq
->m_Command
== "ratinglist")
713 for (const glooxwrapper::Tag
* const& t
: bq
->m_StanzaBoardList
)
715 std::string name
= t
->findAttribute("name").to_string();
716 if (m_PlayerMap
.find(name
) != m_PlayerMap
.end())
717 m_PlayerMap
[name
][1] = t
->findAttribute("rating").to_string();
720 CreateGUIMessage("game", "ratinglist");
725 for (const glooxwrapper::Tag
* const& t
: m_Profile
)
726 glooxwrapper::Tag::free(t
);
729 for (const glooxwrapper::Tag
* const& t
: pq
->m_StanzaProfile
)
730 m_Profile
.emplace_back(t
->clone());
732 CreateGUIMessage("game", "profile");
735 else if (iq
.subtype() == gloox::IQ::Error
)
736 CreateGUIMessage("system", "error", "text", StanzaErrorToString(iq
.error_error()));
739 CreateGUIMessage("system", "error", "text", g_L10n
.Translate("unknown subtype (see logs)"));
740 LOGMESSAGE("unknown subtype '%s'", tag_name(iq
).c_str());
745 /*****************************************************
746 * Presence, nickname, and subject *
747 *****************************************************/
750 * Update local data when a user changes presence.
752 void XmppClient::handleMUCParticipantPresence(glooxwrapper::MUCRoom
*, const glooxwrapper::MUCRoomParticipant participant
, const glooxwrapper::Presence
& presence
)
754 std::string nick
= participant
.nick
->resource().to_string();
755 gloox::Presence::PresenceType presenceType
= presence
.presence();
756 std::string presenceString
, roleString
;
757 GetPresenceString(presenceType
, presenceString
);
758 GetRoleString(participant
.role
, roleString
);
760 if (presenceType
== gloox::Presence::Unavailable
)
762 if (!participant
.newNick
.empty() && (participant
.flags
& (gloox::UserNickChanged
| gloox::UserSelf
)))
764 // we have a nick change
765 std::string newNick
= participant
.newNick
.to_string();
766 m_PlayerMap
[newNick
].resize(3);
767 m_PlayerMap
[newNick
][0] = presenceString
;
768 m_PlayerMap
[newNick
][2] = roleString
;
770 DbgXMPP(nick
<< " is now known as " << participant
.newNick
.to_string());
771 CreateGUIMessage("chat", "nick", "oldnick", nick
, "newnick", participant
.newNick
.to_string());
773 else if (participant
.flags
& gloox::UserKicked
)
775 DbgXMPP(nick
<< " was kicked. Reason: " << participant
.reason
.to_string());
776 CreateGUIMessage("chat", "kicked", "nick", nick
, "reason", participant
.reason
.to_string());
778 else if (participant
.flags
& gloox::UserBanned
)
780 DbgXMPP(nick
<< " was banned. Reason: " << participant
.reason
.to_string());
781 CreateGUIMessage("chat", "banned", "nick", nick
, "reason", participant
.reason
.to_string());
785 DbgXMPP(nick
<< " left the room (flags " << participant
.flags
<< ")");
786 CreateGUIMessage("chat", "leave", "nick", nick
);
788 m_PlayerMap
.erase(nick
);
792 /* During the initialization process, we recieve join messages for everyone
793 * currently in the room. We don't want to display these, so we filter them
794 * out. We will always be the last to join during initialization.
796 if (!m_initialLoadComplete
)
798 if (m_mucRoom
->nick().to_string() == nick
)
799 m_initialLoadComplete
= true;
801 else if (m_PlayerMap
.find(nick
) == m_PlayerMap
.end())
802 CreateGUIMessage("chat", "join", "nick", nick
);
803 else if (m_PlayerMap
[nick
][2] != roleString
)
804 CreateGUIMessage("chat", "role", "nick", nick
, "oldrole", m_PlayerMap
[nick
][2]);
806 CreateGUIMessage("chat", "presence", "nick", nick
);
808 DbgXMPP(nick
<< " is in the room, presence : " << (int)presenceType
);
809 m_PlayerMap
[nick
].resize(3);
810 m_PlayerMap
[nick
][0] = presenceString
;
811 m_PlayerMap
[nick
][2] = roleString
;
816 * Update local cache when subject changes.
818 void XmppClient::handleMUCSubject(glooxwrapper::MUCRoom
*, const glooxwrapper::string
& nick
, const glooxwrapper::string
& subject
)
820 m_Subject
= subject
.c_str();
821 CreateGUIMessage("chat", "subject", "nick", nick
.c_str(), "subject", m_Subject
);
825 * Get current subject.
827 * @param topic Variable to store subject in.
829 void XmppClient::GetSubject(std::string
& subject
)
835 * Request nick change, real change via mucRoomHandler.
837 * @param nick Desired nickname
839 void XmppClient::SetNick(const std::string
& nick
)
841 m_mucRoom
->setNick(nick
);
845 * Get current nickname.
847 * @param nick Variable to store the nickname in.
849 void XmppClient::GetNick(std::string
& nick
)
851 nick
= m_mucRoom
->nick().to_string();
855 * Kick a player from the current room.
857 * @param nick Nickname to be kicked
858 * @param reason Reason the player was kicked
860 void XmppClient::kick(const std::string
& nick
, const std::string
& reason
)
862 m_mucRoom
->kick(nick
, reason
);
866 * Ban a player from the current room.
868 * @param nick Nickname to be banned
869 * @param reason Reason the player was banned
871 void XmppClient::ban(const std::string
& nick
, const std::string
& reason
)
873 m_mucRoom
->ban(nick
, reason
);
877 * Change the xmpp presence of the client.
879 * @param presence A string containing the desired presence
881 void XmppClient::SetPresence(const std::string
& presence
)
883 #define IF(x,y) if (presence == x) m_mucRoom->setPresence(gloox::Presence::y)
884 IF("available", Available
);
885 else IF("chat", Chat
);
886 else IF("away", Away
);
887 else IF("playing", DND
);
888 else IF("offline", Unavailable
);
889 // The others are not to be set
891 else LOGERROR("Unknown presence '%s'", presence
.c_str());
895 * Get the current xmpp presence of the given nick.
897 * @param nick Nickname to look up presence for
898 * @param presence Variable to store the presence in
900 void XmppClient::GetPresence(const std::string
& nick
, std::string
& presence
)
902 if (m_PlayerMap
.find(nick
) != m_PlayerMap
.end())
903 presence
= m_PlayerMap
[nick
][0];
905 presence
= "offline";
909 * Get the current xmpp role of the given nick.
911 * @param nick Nickname to look up presence for
912 * @param role Variable to store the role in
914 void XmppClient::GetRole(const std::string
& nick
, std::string
& role
)
916 if (m_PlayerMap
.find(nick
) != m_PlayerMap
.end())
917 role
= m_PlayerMap
[nick
][2];
922 /*****************************************************
924 *****************************************************/
927 * Parse and return the timestamp of a historic chat message and return the current time for new chat messages.
928 * Historic chat messages are implement as DelayedDelivers as specified in XEP-0203.
929 * Hence, their timestamp MUST be in UTC and conform to the DateTime format XEP-0082.
931 * @returns Seconds since the epoch.
933 std::time_t XmppClient::ComputeTimestamp(const glooxwrapper::Message
& msg
) const
935 // Only historic messages contain a timestamp!
937 return std::time(nullptr);
939 // The locale is irrelevant, because the XMPP date format doesn't contain written month names
940 return g_L10n
.ParseDateTime(msg
.when()->stamp().to_string(), "Y-M-d'T'H:m:sZ", Locale::getUS()) / 1000.0;
944 * Convert a gloox presence type to string.
946 * @param p Presence to be converted
947 * @param presence Variable to store the converted presence string in
949 void XmppClient::GetPresenceString(const gloox::Presence::PresenceType p
, std::string
& presence
) const
953 #define CASE(x,y) case gloox::Presence::x: presence = y; break
954 CASE(Available
, "available");
957 CASE(DND
, "playing");
959 CASE(Unavailable
, "offline");
960 CASE(Probe
, "probe");
961 CASE(Error
, "error");
962 CASE(Invalid
, "invalid");
964 LOGERROR("Unknown presence type '%d'", (int)p
);
971 * Convert a gloox role type to string.
973 * @param p Role to be converted
974 * @param presence Variable to store the converted role string in
976 void XmppClient::GetRoleString(const gloox::MUCRoomRole r
, std::string
& role
) const
980 #define CASE(X, Y) case gloox::X: role = Y; break
981 CASE(RoleNone
, "none");
982 CASE(RoleVisitor
, "visitor");
983 CASE(RoleParticipant
, "participant");
984 CASE(RoleModerator
, "moderator");
985 CASE(RoleInvalid
, "invalid");
987 LOGERROR("Unknown role type '%d'", (int)r
);
994 * Convert a gloox stanza error type to string.
995 * Keep in sync with Gloox documentation
997 * @param err Error to be converted
998 * @return Converted error string
1000 std::string
XmppClient::StanzaErrorToString(gloox::StanzaError err
) const
1002 #define CASE(X, Y) case gloox::X: return Y
1003 #define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")"
1006 CASE(StanzaErrorUndefined
, g_L10n
.Translate("No error"));
1007 DEBUG_CASE(StanzaErrorBadRequest
, "Server recieved malformed XML");
1008 CASE(StanzaErrorConflict
, g_L10n
.Translate("Player already logged in"));
1009 DEBUG_CASE(StanzaErrorFeatureNotImplemented
, "Server does not implement requested feature");
1010 CASE(StanzaErrorForbidden
, g_L10n
.Translate("Forbidden"));
1011 DEBUG_CASE(StanzaErrorGone
, "Unable to find message receipiant");
1012 CASE(StanzaErrorInternalServerError
, g_L10n
.Translate("Internal server error"));
1013 DEBUG_CASE(StanzaErrorItemNotFound
, "Message receipiant does not exist");
1014 DEBUG_CASE(StanzaErrorJidMalformed
, "JID (XMPP address) malformed");
1015 DEBUG_CASE(StanzaErrorNotAcceptable
, "Receipiant refused message. Possible policy issue");
1016 CASE(StanzaErrorNotAllowed
, g_L10n
.Translate("Not allowed"));
1017 CASE(StanzaErrorNotAuthorized
, g_L10n
.Translate("Not authorized"));
1018 DEBUG_CASE(StanzaErrorNotModified
, "Requested item has not changed since last request");
1019 DEBUG_CASE(StanzaErrorPaymentRequired
, "This server requires payment");
1020 CASE(StanzaErrorRecipientUnavailable
, g_L10n
.Translate("Recipient temporarily unavailable"));
1021 DEBUG_CASE(StanzaErrorRedirect
, "Request redirected");
1022 CASE(StanzaErrorRegistrationRequired
, g_L10n
.Translate("Registration required"));
1023 DEBUG_CASE(StanzaErrorRemoteServerNotFound
, "Remote server not found");
1024 DEBUG_CASE(StanzaErrorRemoteServerTimeout
, "Remote server timed out");
1025 DEBUG_CASE(StanzaErrorResourceConstraint
, "The recipient is unable to process the message due to resource constraints");
1026 CASE(StanzaErrorServiceUnavailable
, g_L10n
.Translate("Service unavailable"));
1027 DEBUG_CASE(StanzaErrorSubscribtionRequired
, "Service requires subscription");
1028 DEBUG_CASE(StanzaErrorUnexpectedRequest
, "Attempt to send from invalid stanza address");
1029 DEBUG_CASE(StanzaErrorUnknownSender
, "Invalid 'from' address");
1031 return g_L10n
.Translate("Unknown error");
1038 * Convert a gloox connection error enum to string
1039 * Keep in sync with Gloox documentation
1041 * @param err Error to be converted
1042 * @return Converted error string
1044 std::string
XmppClient::ConnectionErrorToString(gloox::ConnectionError err
) const
1046 #define CASE(X, Y) case gloox::X: return Y
1047 #define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")"
1050 CASE(ConnNoError
, g_L10n
.Translate("No error"));
1051 CASE(ConnStreamError
, g_L10n
.Translate("Stream error"));
1052 CASE(ConnStreamVersionError
, g_L10n
.Translate("The incoming stream version is unsupported"));
1053 CASE(ConnStreamClosed
, g_L10n
.Translate("The stream has been closed by the server"));
1054 DEBUG_CASE(ConnProxyAuthRequired
, "The HTTP/SOCKS5 proxy requires authentication");
1055 DEBUG_CASE(ConnProxyAuthFailed
, "HTTP/SOCKS5 proxy authentication failed");
1056 DEBUG_CASE(ConnProxyNoSupportedAuth
, "The HTTP/SOCKS5 proxy requires an unsupported authentication mechanism");
1057 CASE(ConnIoError
, g_L10n
.Translate("An I/O error occured"));
1058 DEBUG_CASE(ConnParseError
, "An XML parse error occured");
1059 CASE(ConnConnectionRefused
, g_L10n
.Translate("The connection was refused by the server"));
1060 CASE(ConnDnsError
, g_L10n
.Translate("Resolving the server's hostname failed"));
1061 CASE(ConnOutOfMemory
, g_L10n
.Translate("This system is out of memory"));
1062 DEBUG_CASE(ConnNoSupportedAuth
, "The authentication mechanisms the server offered are not supported or no authentication mechanisms were available");
1063 CASE(ConnTlsFailed
, g_L10n
.Translate("The server's certificate could not be verified or the TLS handshake did not complete successfully"));
1064 CASE(ConnTlsNotAvailable
, g_L10n
.Translate("The server did not offer required TLS encryption"));
1065 DEBUG_CASE(ConnCompressionFailed
, "Negotiation/initializing compression failed");
1066 CASE(ConnAuthenticationFailed
, g_L10n
.Translate("Authentication failed. Incorrect password or account does not exist"));
1067 CASE(ConnUserDisconnected
, g_L10n
.Translate("The user or system requested a disconnect"));
1068 CASE(ConnNotConnected
, g_L10n
.Translate("There is no active connection"));
1070 return g_L10n
.Translate("Unknown error");
1077 * Convert a gloox registration result enum to string
1078 * Keep in sync with Gloox documentation
1080 * @param err Enum to be converted
1081 * @return Converted string
1083 std::string
XmppClient::RegistrationResultToString(gloox::RegistrationResult res
) const
1085 #define CASE(X, Y) case gloox::X: return Y
1086 #define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")"
1089 CASE(RegistrationSuccess
, g_L10n
.Translate("Success"));
1090 CASE(RegistrationNotAcceptable
, g_L10n
.Translate("Not all necessary information provided"));
1091 CASE(RegistrationConflict
, g_L10n
.Translate("Username already exists"));
1092 DEBUG_CASE(RegistrationNotAuthorized
, "Account removal timeout or insufficiently secure channel for password change");
1093 DEBUG_CASE(RegistrationBadRequest
, "Server recieved incomplete request");
1094 DEBUG_CASE(RegistrationForbidden
, "Registration forbidden");
1095 DEBUG_CASE(RegistrationRequired
, "Account cannot be removed as it does not exist");
1096 DEBUG_CASE(RegistrationUnexpectedRequest
, "This client is unregistered with the server");
1097 DEBUG_CASE(RegistrationNotAllowed
, "Server does not permit password changes");
1105 void XmppClient::SendStunEndpointToHost(StunClient::StunEndpoint
* stunEndpoint
, const std::string
& hostJIDStr
)
1107 ENSURE(stunEndpoint
);
1109 char ipStr
[256] = "(error)";
1111 addr
.host
= ntohl(stunEndpoint
->ip
);
1112 enet_address_get_host_ip(&addr
, ipStr
, ARRAY_SIZE(ipStr
));
1114 glooxwrapper::JID
hostJID(hostJIDStr
);
1115 glooxwrapper::Jingle::Session session
= m_sessionManager
->createSession(hostJID
);
1116 session
.sessionInitiate(ipStr
, stunEndpoint
->port
);
1119 void XmppClient::handleSessionAction(gloox::Jingle::Action action
, glooxwrapper::Jingle::Session
* UNUSED(session
), const glooxwrapper::Jingle::Session::Jingle
* jingle
)
1121 if (action
== gloox::Jingle::SessionInitiate
)
1122 handleSessionInitiation(jingle
);
1125 void XmppClient::handleSessionInitiation(const glooxwrapper::Jingle::Session::Jingle
* jingle
)
1127 glooxwrapper::Jingle::ICEUDP::Candidate candidate
= jingle
->getCandidate();
1129 if (candidate
.ip
.empty())
1131 LOGERROR("Failed to retrieve Jingle candidate");
1135 g_NetServer
->SendHolePunchingMessage(candidate
.ip
.to_string(), candidate
.port
);