Merge 'remotes/trunk'
[0ad.git] / source / lobby / XmppClient.cpp
bloba86e84d1decd112225669811a768b9d04175e1d9
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"
22 #ifdef WIN32
23 # include <winsock2.h>
24 #endif
26 #include "i18n/L10n.h"
28 #include "lib/external_libraries/enet.h"
29 #include "lib/utf8.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"
37 //debug
38 #if 1
39 #define DbgXMPP(x)
40 #else
41 #define DbgXMPP(x) std::cout << x << std::endl;
43 static std::string tag_xml(const glooxwrapper::IQ& iq)
45 std::string ret;
46 glooxwrapper::Tag* tag = iq.tag();
47 ret = tag->xml().to_string();
48 glooxwrapper::Tag::free(tag);
49 return ret;
51 #endif
53 static std::string tag_name(const glooxwrapper::IQ& iq)
55 std::string ret;
56 glooxwrapper::Tag* tag = iq.tag();
57 ret = tag->name().to_string();
58 glooxwrapper::Tag::free(tag);
59 return ret;
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);
67 /**
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
81 std::string sServer;
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
92 if (!regOpt)
93 m_client = new glooxwrapper::Client(clientJid, sPassword);
94 else
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 );
126 if (!regOpt)
128 // Create a Multi User Chat Room
129 m_mucRoom = new glooxwrapper::MUCRoom(m_client, roomJid, this, 0);
130 // Get room history.
131 m_mucRoom->setRequestHistory(historyRequestSize, gloox::MUCRoom::HistoryMaxStanzas);
133 else
135 // Registration
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;
152 delete m_mucRoom;
154 // Workaround for memory leak in gloox 1.0/1.0.1
155 m_client->removePresenceExtension(gloox::ExtCaps);
157 delete m_client;
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);
167 /// Network
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()
181 m_client->recv(1);
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 *****************************************************/
197 * Handle connection
199 void XmppClient::onConnect()
201 if (m_mucRoom)
203 CreateGUIMessage("system", "connected");
204 m_mucRoom->join();
207 if (m_registration)
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
218 if (m_mucRoom)
219 m_mucRoom->leave();
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);
228 m_BoardList.clear();
229 m_GameList.clear();
230 m_PlayerMap.clear();
231 m_Profile.clear();
232 m_HistoricGuiMessages.clear();
234 CreateGUIMessage("system", "disconnected", "reason", ConnectionErrorToString(error));
238 * Handle TLS connection
240 bool XmppClient::onTLSConnect(const glooxwrapper::CertInfo& info)
242 UNUSED2(info);
243 DbgXMPP("onTLSConnect");
244 DbgXMPP(
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 );
252 return true;
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);
274 // Send IQ
275 BoardListQuery* b = new BoardListQuery();
276 b->m_Command = "getleaderboard";
277 glooxwrapper::IQ iq(gloox::IQ::Get, xpartamuppJid, m_client->getID());
278 iq.addExtension(b);
279 DbgXMPP("SendIqGetBoardList [" << tag_xml(iq) << "]");
280 m_client->send(iq);
284 * Request the profile data from the server.
286 void XmppClient::SendIqGetProfile(const std::string& player)
288 glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
290 // Send IQ
291 ProfileQuery* b = new ProfileQuery();
292 b->m_Command = player;
293 glooxwrapper::IQ iq(gloox::IQ::Get, xpartamuppJid, m_client->getID());
294 iq.addExtension(b);
295 DbgXMPP("SendIqGetProfile [" << tag_xml(iq) << "]");
296 m_client->send(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)
317 std::wstring value;
318 scriptInterface.GetProperty(data, p.c_str(), value);
319 report->addAttribute(p, utf8_from_wstring(value));
322 // Add stanza to IQ
323 game->m_GameReport.emplace_back(report);
325 // Send IQ
326 glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid, m_client->getID());
327 iq.addExtension(game);
328 DbgXMPP("SendGameReport [" << tag_xml(iq) << "]");
329 m_client->send(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)
353 std::wstring value;
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);
361 // Send IQ
362 glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid, m_client->getID());
363 iq.addExtension(g);
364 DbgXMPP("SendIqRegisterGame [" << tag_xml(iq) << "]");
365 m_client->send(iq);
369 * Send a request to unregister a game to the server.
371 void XmppClient::SendIqUnregisterGame()
373 glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
375 // Send IQ
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, m_client->getID());
381 iq.addExtension(g);
382 DbgXMPP("SendIqUnregisterGame [" << tag_xml(iq) << "]");
383 m_client->send(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);
397 // Send IQ
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, m_client->getID());
406 iq.addExtension(g);
407 DbgXMPP("SendIqChangeStateGame [" << tag_xml(iq) << "]");
408 m_client->send(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");
427 else
428 CreateGUIMessage("system", "error", "text", RegistrationResultToString(result));
430 disconnect();
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)
562 GUIMessage message;
563 message.type = type;
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;
569 message.time = time;
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);
588 return ret;
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));
612 uint32_t i = 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());
651 CreateGUIMessage(
652 "chat",
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());
667 CreateGUIMessage(
668 "chat",
669 "private-message",
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);
687 if (gq)
689 for (const glooxwrapper::Tag* const& t : m_GameList)
690 glooxwrapper::Tag::free(t);
691 m_GameList.clear();
693 for (const glooxwrapper::Tag* const& t : gq->m_GameList)
694 m_GameList.emplace_back(t->clone());
696 CreateGUIMessage("game", "gamelist");
698 if (bq)
700 if (bq->m_Command == "boardlist")
702 for (const glooxwrapper::Tag* const& t : m_BoardList)
703 glooxwrapper::Tag::free(t);
704 m_BoardList.clear();
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");
723 if (pq)
725 for (const glooxwrapper::Tag* const& t : m_Profile)
726 glooxwrapper::Tag::free(t);
727 m_Profile.clear();
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()));
737 else
739 CreateGUIMessage("system", "error", "text", g_L10n.Translate("unknown subtype (see logs)"));
740 LOGMESSAGE("unknown subtype '%s'", tag_name(iq).c_str());
742 return true;
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());
783 else
785 DbgXMPP(nick << " left the room (flags " << participant.flags << ")");
786 CreateGUIMessage("chat", "leave", "nick", nick);
788 m_PlayerMap.erase(nick);
790 else
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]);
805 else
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)
831 subject = m_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
890 #undef IF
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];
904 else
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];
918 else
919 role = "";
922 /*****************************************************
923 * Utilities *
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!
936 if (!msg.when())
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
951 switch(p)
953 #define CASE(x,y) case gloox::Presence::x: presence = y; break
954 CASE(Available, "available");
955 CASE(Chat, "chat");
956 CASE(Away, "away");
957 CASE(DND, "playing");
958 CASE(XA, "away");
959 CASE(Unavailable, "offline");
960 CASE(Probe, "probe");
961 CASE(Error, "error");
962 CASE(Invalid, "invalid");
963 default:
964 LOGERROR("Unknown presence type '%d'", (int)p);
965 break;
966 #undef CASE
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
978 switch(r)
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");
986 default:
987 LOGERROR("Unknown role type '%d'", (int)r);
988 break;
989 #undef CASE
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 + ")"
1004 switch (err)
1006 CASE(StanzaErrorUndefined, g_L10n.Translate("No error"));
1007 DEBUG_CASE(StanzaErrorBadRequest, "Server received 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");
1030 default:
1031 return g_L10n.Translate("Unknown error");
1033 #undef DEBUG_CASE
1034 #undef CASE
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 + ")"
1048 switch (err)
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 occurred"));
1058 DEBUG_CASE(ConnParseError, "An XML parse error occurred");
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"));
1069 default:
1070 return g_L10n.Translate("Unknown error");
1072 #undef DEBUG_CASE
1073 #undef CASE
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 + ")"
1087 switch (res)
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 received an 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");
1098 default:
1099 return "";
1101 #undef DEBUG_CASE
1102 #undef CASE
1105 void XmppClient::SendStunEndpointToHost(StunClient::StunEndpoint* stunEndpoint, const std::string& hostJIDStr)
1107 ENSURE(stunEndpoint);
1109 char ipStr[256] = "(error)";
1110 ENetAddress addr;
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");
1132 return;
1135 g_NetServer->SendHolePunchingMessage(candidate.ip.to_string(), candidate.port);