Slack: Store channel/im/user name to ID mapping in SlackIdManager

This commit is contained in:
Jan Kaluza 2016-01-29 13:45:28 +01:00
parent 3a7c516129
commit b55393bf9f
11 changed files with 267 additions and 90 deletions

View file

@ -22,6 +22,7 @@
#include "SlackFrontend.h"
#include "SlackUser.h"
#include "SlackRTM.h"
#include "SlackIdManager.h"
#include "transport/Transport.h"
#include "transport/HTTPRequest.h"
@ -70,9 +71,10 @@ DEFINE_LOGGER(logger, "SlackAPI");
NAME = NAME##_tmp.GetString(); \
}
SlackAPI::SlackAPI(Component *component, const std::string &token) : HTTPRequestQueue(component) {
SlackAPI::SlackAPI(Component *component, SlackIdManager *idManager, const std::string &token) : HTTPRequestQueue(component) {
m_component = component;
m_token = token;
m_idManager = idManager;
}
SlackAPI::~SlackAPI() {
@ -353,34 +355,45 @@ std::string SlackAPI::SlackObjectToPlainText(const std::string &object, bool isC
return ret;
}
void SlackAPI::handleSlackChannelInvite(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data, const std::string &channel, const std::string &user, CreateChannelCallback callback) {
void SlackAPI::handleSlackChannelInvite(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data, const std::string &channel, const std::string &userId, CreateChannelCallback callback) {
callback(channel);
}
void SlackAPI::handleSlackChannelCreate(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data, const std::string &channel, const std::string &user, CreateChannelCallback callback) {
void SlackAPI::handleSlackChannelCreate(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data, const std::string &channel, const std::string &userId, CreateChannelCallback callback) {
std::string channelId = getChannelId(req, ok, resp, data);
if (channelId.empty()) {
LOG4CXX_INFO(logger, "Error creating channel " << channel << ".");
return;
}
channelsInvite(channelId, user, boost::bind(&SlackAPI::handleSlackChannelInvite, this, _1, _2, _3, _4, channelId, user, callback));
channelsInvite(channelId, userId, boost::bind(&SlackAPI::handleSlackChannelInvite, this, _1, _2, _3, _4, channelId, userId, callback));
}
void SlackAPI::handleSlackChannelList(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data, const std::string &channel, const std::string &user, CreateChannelCallback callback) {
std::map<std::string, SlackChannelInfo> channels;
void SlackAPI::handleSlackChannelList(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data, const std::string &channel, const std::string &userId, CreateChannelCallback callback) {
std::map<std::string, SlackChannelInfo> &channels = m_idManager->getChannels();
SlackAPI::getSlackChannelInfo(req, ok, resp, data, channels);
if (channels.find(channel) != channels.end()) {
channelsInvite(channel, user, boost::bind(&SlackAPI::handleSlackChannelInvite, this, _1, _2, _3, _4, channels[channel].id, user, callback));
channelsInvite(channel, userId, boost::bind(&SlackAPI::handleSlackChannelInvite, this, _1, _2, _3, _4, channels[channel].id, userId, callback));
}
else {
channelsCreate(channel, boost::bind(&SlackAPI::handleSlackChannelCreate, this, _1, _2, _3, _4, channel, user, callback));
channelsCreate(channel, boost::bind(&SlackAPI::handleSlackChannelCreate, this, _1, _2, _3, _4, channel, userId, callback));
}
}
void SlackAPI::createChannel(const std::string &channel, const std::string &user, CreateChannelCallback callback) {
channelsList(boost::bind(&SlackAPI::handleSlackChannelList, this, _1, _2, _3, _4, channel, user, callback));
void SlackAPI::createChannel(const std::string &channel, const std::string &userId, CreateChannelCallback callback) {
std::string channelId = m_idManager->getId(channel);
if (channelId != channel) {
if (m_idManager->hasMember(channelId, userId)) {
callback(channelId);
}
else {
channelsInvite(channel, userId, boost::bind(&SlackAPI::handleSlackChannelInvite, this, _1, _2, _3, _4, channelId, userId, callback));
}
}
else {
channelsList(boost::bind(&SlackAPI::handleSlackChannelList, this, _1, _2, _3, _4, channel, userId, callback));
}
}

View file

@ -35,6 +35,7 @@ namespace Transport {
class Component;
class StorageBackend;
class HTTPRequest;
class SlackIdManager;
class SlackChannelInfo {
public:
@ -68,7 +69,7 @@ class SlackUserInfo {
class SlackAPI : public HTTPRequestQueue {
public:
SlackAPI(Component *component, const std::string &token);
SlackAPI(Component *component, SlackIdManager *idManager, const std::string &token);
virtual ~SlackAPI();
@ -104,6 +105,7 @@ class SlackAPI : public HTTPRequestQueue {
private:
Component *m_component;
SlackIdManager *m_idManager;
std::string m_token;
};

View file

@ -0,0 +1,77 @@
/**
* XMPP - libpurple transport
*
* Copyright (C) 2009, Jan Kaluza <hanzz@soc.pidgin.im>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include "SlackIdManager.h"
#include "SlackFrontend.h"
#include "SlackUser.h"
#include "transport/Transport.h"
#include "transport/HTTPRequest.h"
#include "transport/Util.h"
#include "transport/WebSocketClient.h"
#include <boost/foreach.hpp>
#include <boost/make_shared.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include <map>
#include <iterator>
namespace Transport {
DEFINE_LOGGER(logger, "SlackIdManager");
SlackIdManager::SlackIdManager() {
}
SlackIdManager::~SlackIdManager() {
}
const std::string &SlackIdManager::getName(const std::string &id) {
if (m_users.find(id) == m_users.end()) {
return id;
}
return m_users[id].name;
}
const std::string &SlackIdManager::getId(const std::string &name) {
for (std::map<std::string, SlackChannelInfo>::const_iterator it = m_channels.begin(); it != m_channels.end(); ++it) {
if (it->second.name == name) {
return it->second.id;
}
}
return name;
}
bool SlackIdManager::hasMember(const std::string &channelId, const std::string &userId) {
if (m_channels.find(channelId) == m_channels.end()) {
return false;
}
SlackChannelInfo &channel = m_channels[channelId];
return std::find(channel.members.begin(), channel.members.end(), userId) != channel.members.end();
}
}

View file

@ -0,0 +1,101 @@
/**
* Spectrum 2 Slack Frontend
*
* Copyright (C) 2015, Jan Kaluza <hanzz.k@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#pragma once
#include "SlackAPI.h"
#include "transport/StorageBackend.h"
#include "rapidjson/document.h"
#include <Swiften/Network/TLSConnectionFactory.h>
#include <Swiften/Network/HostAddressPort.h>
#include <Swiften/TLS/PlatformTLSFactories.h>
#include <Swiften/Network/DomainNameResolveError.h>
#include <Swiften/Network/DomainNameAddressQuery.h>
#include <Swiften/Network/DomainNameResolver.h>
#include <Swiften/Network/HostAddress.h>
#include <Swiften/Network/Connection.h>
#include <Swiften/Base/SafeByteArray.h>
#include "Swiften/Network/Timer.h"
#include "Swiften/Version.h"
#define HAVE_SWIFTEN_3 (SWIFTEN_VERSION >= 0x030000)
#if HAVE_SWIFTEN_3
#include <Swiften/TLS/TLSOptions.h>
#endif
#include <string>
#include <algorithm>
#include <map>
#include <boost/signal.hpp>
namespace Transport {
class SlackIdManager {
public:
SlackIdManager();
virtual ~SlackIdManager();
std::map<std::string, SlackUserInfo> &getUsers() {
return m_users;
}
std::map<std::string, SlackChannelInfo> &getChannels() {
return m_channels;
}
std::map<std::string, SlackImInfo> &getIMs() {
return m_ims;
}
const std::string &getName(const std::string &id);
const std::string &getId(const std::string &name);
bool hasMember(const std::string &channelId, const std::string &userId);
const std::string &getSelfName() {
return m_selfName;
}
const std::string &getSelfId() {
return m_selfId;
}
void setSelfName(const std::string &name) {
m_selfName = name;
}
void setSelfId(const std::string &id) {
m_selfId = id;
}
private:
std::map<std::string, SlackChannelInfo> m_channels;
std::map<std::string, SlackImInfo> m_ims;
std::map<std::string, SlackUserInfo> m_users;
std::string m_selfName;
std::string m_selfId;
};
}

View file

@ -21,6 +21,7 @@
#include "SlackRTM.h"
#include "SlackFrontend.h"
#include "SlackUser.h"
#include "SlackIdManager.h"
#include "transport/Transport.h"
#include "transport/HTTPRequest.h"
@ -38,11 +39,12 @@ namespace Transport {
DEFINE_LOGGER(logger, "SlackRTM");
SlackRTM::SlackRTM(Component *component, StorageBackend *storageBackend, UserInfo uinfo) : m_uinfo(uinfo) {
SlackRTM::SlackRTM(Component *component, StorageBackend *storageBackend, SlackIdManager *idManager, UserInfo uinfo) : m_uinfo(uinfo) {
m_component = component;
m_storageBackend = storageBackend;
m_counter = 0;
m_started = false;
m_idManager = idManager;
m_client = new WebSocketClient(component);
m_client->onPayloadReceived.connect(boost::bind(&SlackRTM::handlePayloadReceived, this, _1));
m_client->onWebSocketConnected.connect(boost::bind(&SlackRTM::handleWebSocketConnected, this));
@ -53,7 +55,7 @@ SlackRTM::SlackRTM(Component *component, StorageBackend *storageBackend, UserInf
int type = (int) TYPE_STRING;
m_storageBackend->getUserSetting(m_uinfo.id, "bot_token", type, m_token);
m_api = new SlackAPI(component, m_token);
m_api = new SlackAPI(component, m_idManager, m_token);
std::string url = "https://slack.com/api/rtm.start?";
url += "token=" + Util::urlencode(m_token);
@ -146,14 +148,6 @@ void SlackRTM::sendPing() {
m_pingTimer->start();
}
const std::string &SlackRTM::getUserName(const std::string &id) {
if (m_users.find(id) == m_users.end()) {
return id;
}
return m_users[id].name;
}
void SlackRTM::handleRTMStart(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data) {
if (!ok) {
LOG4CXX_ERROR(logger, req->getError());
@ -182,7 +176,7 @@ void SlackRTM::handleRTMStart(HTTPRequest *req, bool ok, rapidjson::Document &re
return;
}
m_selfName = selfName.GetString();
m_idManager->setSelfName(selfName.GetString());
rapidjson::Value &selfId = self["id"];
if (!selfId.IsString()) {
@ -191,11 +185,11 @@ void SlackRTM::handleRTMStart(HTTPRequest *req, bool ok, rapidjson::Document &re
return;
}
m_selfId = selfId.GetString();
m_idManager->setSelfId(selfId.GetString());
SlackAPI::getSlackChannelInfo(req, ok, resp, data, m_channels);
SlackAPI::getSlackImInfo(req, ok, resp, data, m_ims);
SlackAPI::getSlackUserInfo(req, ok, resp, data, m_users);
SlackAPI::getSlackChannelInfo(req, ok, resp, data, m_idManager->getChannels());
SlackAPI::getSlackImInfo(req, ok, resp, data, m_idManager->getIMs());
SlackAPI::getSlackUserInfo(req, ok, resp, data, m_idManager->getUsers());
std::string u = url.GetString();
LOG4CXX_INFO(logger, "Started RTM, WebSocket URL is " << u);

View file

@ -56,10 +56,11 @@ class StorageBackend;
class HTTPRequest;
class WebSocketClient;
class SlackAPI;
class SlackIdManager;
class SlackRTM {
public:
SlackRTM(Component *component, StorageBackend *storageBackend, UserInfo uinfo);
SlackRTM(Component *component, StorageBackend *storageBackend, SlackIdManager *idManager, UserInfo uinfo);
virtual ~SlackRTM();
@ -69,43 +70,18 @@ class SlackRTM {
boost::signal<void ()> onRTMStarted;
std::map<std::string, SlackUserInfo> &getUsers() {
return m_users;
}
std::map<std::string, SlackChannelInfo> &getChannels() {
return m_channels;
}
SlackAPI *getAPI() {
return m_api;
}
boost::signal<void (const std::string &channel, const std::string &user, const std::string &text, const std::string &ts)> onMessageReceived;
const std::string &getUserName(const std::string &id);
const std::string &getSelfName() {
return m_selfName;
}
const std::string &getSelfId() {
return m_selfId;
}
private:
void handlePayloadReceived(const std::string &payload);
void handleRTMStart(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data);
void handleWebSocketConnected();
void handleWebSocketDisconnected(const boost::optional<Swift::Connection::Error> &error);
private:
std::map<std::string, SlackChannelInfo> m_channels;
std::map<std::string, SlackImInfo> m_ims;
std::map<std::string, SlackUserInfo> m_users;
std::string m_selfName;
std::string m_selfId;
private:
Component *m_component;
StorageBackend *m_storageBackend;
@ -116,6 +92,7 @@ class SlackRTM {
Swift::Timer::ref m_pingTimer;
SlackAPI *m_api;
bool m_started;
SlackIdManager *m_idManager;
};
}

View file

@ -23,6 +23,7 @@
#include "SlackUser.h"
#include "SlackRTM.h"
#include "SlackRosterManager.h"
#include "SlackIdManager.h"
#include "transport/Transport.h"
#include "transport/HTTPRequest.h"
@ -50,7 +51,9 @@ SlackSession::SlackSession(Component *component, StorageBackend *storageBackend,
m_component = component;
m_storageBackend = storageBackend;
m_rtm = new SlackRTM(component, storageBackend, uinfo);
m_idManager = new SlackIdManager();
m_rtm = new SlackRTM(component, storageBackend, m_idManager, uinfo);
m_rtm->onRTMStarted.connect(boost::bind(&SlackSession::handleRTMStarted, this));
m_rtm->onMessageReceived.connect(boost::bind(&SlackSession::handleMessageReceived, this, _1, _2, _3, _4, false));
@ -60,12 +63,13 @@ SlackSession::SlackSession(Component *component, StorageBackend *storageBackend,
int type = (int) TYPE_STRING;
std::string token;
m_storageBackend->getUserSetting(m_uinfo.id, "access_token", type, token);
m_api = new SlackAPI(m_component, token);
m_api = new SlackAPI(m_component, m_idManager, token);
}
SlackSession::~SlackSession() {
delete m_rtm;
delete m_api;
delete m_idManager;
m_onlineBuddiesTimer->stop();
}
@ -184,7 +188,7 @@ void SlackSession::handleJoinRoomCreated(const std::string &channelId, std::vect
void SlackSession::handleJoinMessage(const std::string &message, std::vector<std::string> &args, bool quiet) {
LOG4CXX_INFO(logger, args[1] << ": Going to join the room, checking the ID of channel " << args[5]);
m_api->createChannel(args[5], m_rtm->getSelfId(), boost::bind(&SlackSession::handleJoinRoomCreated, this, _1, args));
m_api->createChannel(args[5], m_idManager->getSelfId(), boost::bind(&SlackSession::handleJoinRoomCreated, this, _1, args));
}
void SlackSession::handleSlackChannelCreated(const std::string &channelId) {
@ -198,37 +202,14 @@ void SlackSession::handleSlackChannelCreated(const std::string &channelId) {
m_component->getFrontend()->onPresenceReceived(presence);
}
void SlackSession::handleLeaveMessage(const std::string &message, std::vector<std::string> &args, bool quiet) {
// .spectrum2 leave.room channel
std::string slackChannel = SlackAPI::SlackObjectToPlainText(args[2], true);
std::string to = m_channel2jid[slackChannel];
void SlackSession::leaveRoom(const std::string &channel) {
std::string channelId = m_idManager->getId(channel);
std::string to = m_channel2jid[channel];
if (to.empty()) {
m_rtm->sendMessage(m_ownerChannel, "Spectrum 2 is not configured to transport this Slack channel.");
return;
}
std::string rooms = "";
int type = (int) TYPE_STRING;
m_storageBackend->getUserSetting(m_uinfo.id, "rooms", type, rooms);
std::vector<std::string> commands;
boost::split(commands, rooms, boost::is_any_of("\n"));
rooms = "";
BOOST_FOREACH(const std::string &command, commands) {
if (command.size() > 5) {
std::vector<std::string> args2;
boost::split(args2, command, boost::is_any_of(" "));
if (args2.size() == 6) {
if (slackChannel != SlackAPI::SlackObjectToPlainText(args2[5], true)) {
rooms += command + "\n";
}
}
}
}
m_storageBackend->updateUserSetting(m_uinfo.id, "rooms", rooms);
Swift::Presence::ref presence = Swift::Presence::create();
presence->setFrom(Swift::JID("", m_uinfo.jid, "default"));
presence->setTo(Swift::JID(to + "/" + m_uinfo.uin));
@ -264,7 +245,7 @@ void SlackSession::handleRegisterMessage(const std::string &message, std::vector
void SlackSession::handleMessageReceived(const std::string &channel, const std::string &user, const std::string &message, const std::string &ts, bool quiet) {
if (m_ownerChannel != channel) {
std::string to = m_channel2jid[channel];
if (m_rtm->getUserName(user) == m_rtm->getSelfName()) {
if (m_idManager->getName(user) == m_idManager->getSelfName()) {
return;
}
@ -273,7 +254,7 @@ void SlackSession::handleMessageReceived(const std::string &channel, const std::
msg->setType(Swift::Message::Groupchat);
msg->setTo(to);
msg->setFrom(Swift::JID("", m_uinfo.jid, "default"));
msg->setBody("<" + m_rtm->getUserName(user) + "> " + message);
msg->setBody("<" + m_idManager->getName(user) + "> " + message);
m_component->getFrontend()->onMessageReceived(msg);
}
else {
@ -309,7 +290,7 @@ void SlackSession::handleMessageReceived(const std::string &channel, const std::
boost::shared_ptr<Swift::Message> msg(new Swift::Message());
msg->setTo(b->getJID());
msg->setFrom(Swift::JID("", m_uinfo.jid, "default"));
msg->setBody("<" + m_rtm->getUserName(user) + "> " + message);
msg->setBody("<" + m_idManager->getName(user) + "> " + message);
m_component->getFrontend()->onMessageReceived(msg);
}
}
@ -328,7 +309,7 @@ void SlackSession::handleMessageReceived(const std::string &channel, const std::
handleJoinMessage(message, args, quiet);
}
else if (args[1] == "leave.room" && args.size() == 3) {
handleLeaveMessage(message, args, quiet);
// handleLeaveMessage(message, args, quiet);
}
else if (args[1] == "register" && args.size() == 5) {
handleRegisterMessage(message, args, quiet);
@ -435,7 +416,7 @@ void SlackSession::handleImOpen(HTTPRequest *req, bool ok, rapidjson::Document &
else {
m_storageBackend->getUserSetting(m_uinfo.id, "slack_channel", type, m_slackChannel);
if (!m_slackChannel.empty()) {
m_api->createChannel(m_slackChannel, m_rtm->getSelfId(), boost::bind(&SlackSession::handleSlackChannelCreated, this, _1));
m_api->createChannel(m_slackChannel, m_idManager->getSelfId(), boost::bind(&SlackSession::handleSlackChannelCreated, this, _1));
}
else {
std::string msg;
@ -465,7 +446,7 @@ void SlackSession::handleImOpen(HTTPRequest *req, bool ok, rapidjson::Document &
}
void SlackSession::handleRTMStarted() {
std::map<std::string, SlackUserInfo> &users = m_rtm->getUsers();
std::map<std::string, SlackUserInfo> &users = m_idManager->getUsers();
for (std::map<std::string, SlackUserInfo>::iterator it = users.begin(); it != users.end(); it++) {
SlackUserInfo &info = it->second;
if (info.isPrimaryOwner) {

View file

@ -40,6 +40,7 @@ class HTTPRequest;
class SlackRTM;
class SlackAPI;
class User;
class SlackIdManager;
class SlackSession {
public:
@ -61,7 +62,7 @@ class SlackSession {
void handleConnected();
void handleJoinMessage(const std::string &message, std::vector<std::string> &args, bool quiet = false);
void handleLeaveMessage(const std::string &message, std::vector<std::string> &args, bool quiet = false);
void leaveRoom(const std::string &channel);
void handleRegisterMessage(const std::string &message, std::vector<std::string> &args, bool quiet = false);
private:
@ -90,6 +91,7 @@ class SlackSession {
bool m_disconnected;
std::string m_ownerId;
SlackAPI *m_api;
SlackIdManager *m_idManager;
};
}

View file

@ -185,8 +185,7 @@ bool SlackUserManager::handleAdminMessage(Swift::Message::ref message) {
SlackUser *user = static_cast<SlackUser *>(getUser(args[1]));
if (user) {
// TODO
// user->getSession()->handleJoinMessage("", args, true);
user->getSession()->leaveRoom(args[2]);
}
message->setBody("Left the room");
return true;

View file

@ -306,6 +306,33 @@ void APIServer::serve_instances_join_room(Server *server, Server::session *sessi
}
}
void APIServer::serve_instances_leave_room(Server *server, Server::session *session, struct mg_connection *conn, struct http_message *hm) {
std::string uri(hm->uri.p, hm->uri.len);
std::string instance = uri.substr(uri.rfind("/") + 1);
UserInfo info;
m_storage->getUser(session->user, info);
std::string username = "";
int type = (int) TYPE_STRING;
m_storage->getUserSetting(info.id, instance, type, username);
if (username.empty()) {
send_ack(conn, true, "You are not registered to this Spectrum 2 instance.");
return;
}
std::string frontend_room = get_http_var(hm, "frontend_room");
std::string response = server->send_command(instance, "leave_room " + username + " " + frontend_room);
if (response.find("Left the room") == std::string::npos) {
send_ack(conn, true, response);
}
else {
send_ack(conn, false, response);
}
}
void APIServer::serve_instances_join_room_form(Server *server, Server::session *session, struct mg_connection *conn, struct http_message *hm) {
// So far we support just Slack here. For XMPP, it is up to user to initiate the join room request.
Document json;
@ -436,6 +463,9 @@ void APIServer::handleRequest(Server *server, Server::session *sess, struct mg_c
else if (has_prefix(&hm->uri, "/api/v1/instances/list_rooms/")) {
serve_instances_list_rooms(server, sess, conn, hm);
}
else if (has_prefix(&hm->uri, "/api/v1/instances/leave_room/")) {
serve_instances_leave_room(server, sess, conn, hm);
}
else if (has_prefix(&hm->uri, "/api/v1/users/remove/")) {
serve_users_remove(server, sess, conn, hm);
}

View file

@ -61,6 +61,7 @@ class APIServer {
void serve_instances_list_rooms(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_instances_join_room(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_instances_join_room_form(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_instances_leave_room(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_users(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_users_add(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_users_remove(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);