Changeset - 24d902983a17
[Not reviewed]
0 8 0
Jan Kaluza - 10 years ago 2015-12-07 18:17:04
jkaluza@redhat.com
Send list of online users to Slack channel when transporting to 3rd-party network room.
8 files changed with 123 insertions and 15 deletions:
0 comments (0 inline, 0 general)
backends/libpurple/main.cpp
Show inline comments
 
@@ -661,25 +661,29 @@ class SpectrumNetworkPlugin : public NetworkPlugin {
 
				return;
 
			}
 

	
 
			PurpleConnection *gc = purple_account_get_connection_wrapped(account);
 
			GHashTable *comps = NULL;
 

	
 
			// Check if the PurpleChat is not stored in buddy list
 
			PurpleChat *chat = purple_blist_find_chat_wrapped(account, room.c_str());
 
			if (chat) {
 
				comps = purple_chat_get_components_wrapped(chat);
 
			}
 
			else if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults != NULL) {
 
				comps = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, room.c_str());
 
				if (CONFIG_STRING(config, "service.protocol") == "prpl-jabber") {
 
					comps = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, (room + "/" + nickname).c_str());
 
				} else {
 
					comps = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, room.c_str());
 
				}
 
			}
 

	
 
			LOG4CXX_INFO(logger, user << ": Joining the room " << room);
 
			if (comps) {
 
				serv_join_chat_wrapped(gc, comps);
 
				g_hash_table_destroy(comps);
 
			}
 
		}
 

	
 
		void handleLeaveRoomRequest(const std::string &user, const std::string &room) {
 
			PurpleAccount *account = m_sessions[user];
 
			if (!account) {
include/transport/Conversation.h
Show inline comments
 
@@ -137,24 +137,25 @@ class Conversation {
 
		/// \param room room name associated with this Conversation.
 
		void setRoom(const std::string &room);
 

	
 
		/// Returns room name associated with this Conversation.
 

	
 
		/// \return room name associated with this Conversation.
 
		const std::string &getRoom() {
 
			return m_room;
 
		}
 

	
 
		void destroyRoom();
 

	
 
		std::string getParticipants();
 
		void sendParticipants(const Swift::JID &to);
 

	
 
		void sendCachedMessages(const Swift::JID &to = Swift::JID());
 

	
 
	private:
 
		Swift::Presence::ref generatePresence(const std::string &nick, int flag, int status, const std::string &statusMessage, const std::string &newname = "");
 
		void cacheMessage(boost::shared_ptr<Swift::Message> &message);
 

	
 
	private:
 
		ConversationManager *m_conversationManager;
 
		std::string m_legacyName;
 
		std::string m_nickname;
include/transport/ConversationManager.h
Show inline comments
 
@@ -69,23 +69,27 @@ class ConversationManager {
 

	
 
		/// Removes Conversation from the manager.
 

	
 
		/// \param conv Conversation.
 
		void removeConversation(Conversation *conv);
 

	
 
		void deleteAllConversations();
 

	
 
		void resetResources();
 
		void removeJID(const Swift::JID &jid);
 
		void clearJIDs();
 

	
 
		std::map<std::string, Conversation *> &getConversations() {
 
			return m_convs;
 
		}
 

	
 
	private:
 
		void handleMessageReceived(Swift::Message::ref message);
 

	
 
		Component *m_component;
 
		User *m_user;
 

	
 
		std::map<std::string, Conversation *> m_convs;
 
		friend class UserManager;
 
};
 

	
 
}
spectrum/src/frontends/slack/SlackRTM.cpp
Show inline comments
 
@@ -101,38 +101,62 @@ void SlackRTM::sendMessage(const std::string &channel, const std::string &messag
 
	boost::replace_all(m, "\"", "\\\"");
 
	std::string msg = "{\"id\": " + boost::lexical_cast<std::string>(m_counter) + ", \"type\": \"message\", \"channel\":\"" + channel + "\", \"text\":\"" + m + "\"}";
 
	m_client->write(msg);
 
}
 

	
 
void SlackRTM::sendPing() {
 
	m_counter++;
 
	std::string msg = "{\"id\": " + boost::lexical_cast<std::string>(m_counter) + ", \"type\": \"ping\"}";
 
	m_client->write(msg);
 
	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());
 
		LOG4CXX_ERROR(logger, data);
 
		return;
 
	}
 

	
 
	rapidjson::Value &url = resp["url"];
 
	if (!url.IsString()) {
 
		LOG4CXX_ERROR(logger, "No 'url' object in the reply.");
 
		LOG4CXX_ERROR(logger, data);
 
		return;
 
	}
 

	
 
	rapidjson::Value &self = resp["self"];
 
	if (!self.IsObject()) {
 
		LOG4CXX_ERROR(logger, "No 'self' object in the reply.");
 
		LOG4CXX_ERROR(logger, data);
 
		return;
 
	}
 

	
 
	rapidjson::Value &selfName = self["name"];
 
	if (!selfName.IsString()) {
 
		LOG4CXX_ERROR(logger, "No 'name' string in the reply.");
 
		LOG4CXX_ERROR(logger, data);
 
		return;
 
	}
 

	
 
	m_selfName = selfName.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);
 

	
 
	std::string u = url.GetString();
 
	LOG4CXX_INFO(logger, "Started RTM, WebSocket URL is " << u);
 
	LOG4CXX_INFO(logger, data);
 

	
 
	m_client->connectServer(u);
 
	m_pingTimer->start();
 
}
 

	
spectrum/src/frontends/slack/SlackRTM.h
Show inline comments
 
@@ -73,33 +73,40 @@ class SlackRTM {
 
		}
 

	
 
		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;
 
		}
 

	
 
	private:
 
		void handlePayloadReceived(const std::string &payload);
 
		void handleRTMStart(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data);
 
		void handleWebSocketConnected();
 

	
 
	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;
 

	
 
	private:
 
		Component *m_component;
 
		StorageBackend *m_storageBackend;
 
		UserInfo m_uinfo;
 
		WebSocketClient *m_client;
 
		std::string m_token;
 
		unsigned long m_counter;
 
		Swift::Timer::ref m_pingTimer;
 
		SlackAPI *m_api;
 
};
 

	
spectrum/src/frontends/slack/SlackSession.cpp
Show inline comments
 
@@ -20,122 +20,173 @@
 

	
 
#include "SlackSession.h"
 
#include "SlackFrontend.h"
 
#include "SlackUser.h"
 
#include "SlackRTM.h"
 
#include "SlackRosterManager.h"
 

	
 
#include "transport/Transport.h"
 
#include "transport/HTTPRequest.h"
 
#include "transport/Util.h"
 
#include "transport/Buddy.h"
 
#include "transport/Config.h"
 
#include "transport/ConversationManager.h"
 
#include "transport/Conversation.h"
 

	
 
#include <boost/foreach.hpp>
 
#include <boost/make_shared.hpp>
 
#include <boost/lexical_cast.hpp>
 
#include <boost/algorithm/string.hpp>
 

	
 
#include "Swiften/Elements/MUCPayload.h"
 

	
 
#include <map>
 
#include <iterator>
 

	
 
namespace Transport {
 

	
 
DEFINE_LOGGER(logger, "SlackSession");
 

	
 
SlackSession::SlackSession(Component *component, StorageBackend *storageBackend, UserInfo uinfo) : m_uinfo(uinfo), m_user(NULL) {
 
	m_component = component;
 
	m_storageBackend = storageBackend;
 

	
 
	m_rtm = new SlackRTM(component, storageBackend, uinfo);
 
	m_rtm->onRTMStarted.connect(boost::bind(&SlackSession::handleRTMStarted, this));
 
	m_rtm->onMessageReceived.connect(boost::bind(&SlackSession::handleMessageReceived, this, _1, _2, _3, _4, false));
 

	
 
	m_onlineBuddiesTimer = m_component->getNetworkFactories()->getTimerFactory()->createTimer(20000);
 
	m_onlineBuddiesTimer->onTick.connect(boost::bind(&SlackSession::sendOnlineBuddies, this));
 
}
 

	
 
SlackSession::~SlackSession() {
 
	delete m_rtm;
 
	m_onlineBuddiesTimer->stop();
 
}
 

	
 
void SlackSession::sendMessage(boost::shared_ptr<Swift::Message> message) {
 
	if (message->getFrom().getResource() == m_uinfo.uin) {
 
void SlackSession::sendOnlineBuddies() {
 
	if (!m_user) {
 
		return;
 
	}
 
	std::map<std::string, Conversation *> convs = m_user->getConversationManager()->getConversations();
 
	for (std::map<std::string, Conversation *> ::const_iterator it = convs.begin(); it != convs.end(); it++) {
 
		Conversation *conv = it->second;
 
		if (!conv) {
 
			continue;
 
		}
 

	
 
		std::string onlineBuddies = "Online users: " + conv->getParticipants();
 

	
 
		if (m_onlineBuddies[it->first] != onlineBuddies) {
 
			m_onlineBuddies[it->first] = onlineBuddies;
 
			std::string legacyName = it->first;
 
			if (legacyName.find_last_of("@") != std::string::npos) {
 
				legacyName.replace(legacyName.find_last_of("@"), 1, "%"); // OK
 
			}
 

	
 

	
 
			std::string to = legacyName + "@" + m_component->getJID().toBare().toString();
 
			setPurpose(onlineBuddies, m_jid2channel[to]);
 
		}
 
	}
 
	m_onlineBuddiesTimer->start();
 
}
 

	
 
void SlackSession::sendMessage(boost::shared_ptr<Swift::Message> message) {
 
	if (m_user) {
 
		std::map<std::string, Conversation *> convs = m_user->getConversationManager()->getConversations();
 
		for (std::map<std::string, Conversation *> ::const_iterator it = convs.begin(); it != convs.end(); it++) {
 
			Conversation *conv = it->second;
 
			if (!conv) {
 
				continue;
 
			}
 

	
 
			if (conv->getNickname() == message->getFrom().getResource()) {
 
				return;
 
			}
 
		}
 
	}
 

	
 
	std::string from = message->getFrom().getResource();
 
	std::string channel = m_jid2channel[message->getFrom().toBare().toString()];
 
	LOG4CXX_INFO(logger, "JID is " << message->getFrom().toBare().toString());
 
	if (channel.empty()) {
 
		if (m_slackChannel.empty()) {
 
			LOG4CXX_ERROR(logger, m_uinfo.jid << ": Received message for unknown channel from " << message->getFrom().toBare().toString());
 
			return;
 
		}
 
		channel = m_slackChannel;
 
		from = Buddy::JIDToLegacyName(message->getFrom());
 

	
 
		Buddy *b;
 
		if (m_user && (b = m_user->getRosterManager()->getBuddy(from)) != NULL) {
 
			from = b->getAlias() + " (" + from + ")";
 
		}
 
	}
 

	
 
	LOG4CXX_INFO(logger, "FROM " << from);
 
	m_rtm->getAPI()->sendMessage(from, channel, message->getBody());
 
}
 

	
 
void SlackSession::setPurpose(const std::string &purpose) {
 
	if (m_slackChannel.empty()) {
 
void SlackSession::setPurpose(const std::string &purpose, const std::string &channel) {
 
	std::string ch = channel;
 
	if (ch.empty()) {
 
		ch = m_slackChannel;
 
	}
 
	if (ch.empty()) {
 
		return;
 
	}
 

	
 
	LOG4CXX_INFO(logger, "Setting channel purppose: " << m_slackChannel << " " << purpose);
 
	m_rtm->getAPI()->setPurpose(m_slackChannel, purpose);
 
	LOG4CXX_INFO(logger, "Setting channel purppose: " << ch << " " << purpose);
 
	m_rtm->getAPI()->setPurpose(ch, purpose);
 
}
 

	
 
void SlackSession::handleJoinMessage(const std::string &message, std::vector<std::string> &args, bool quiet) {
 
	// .spectrum2 join.room BotName #room irc.freenode.net channel
 
	std::string &name = args[2];
 
	std::string legacyRoom = SlackAPI::SlackObjectToPlainText(args[3], true);
 
	std::string legacyServer = SlackAPI::SlackObjectToPlainText(args[4]);
 
	std::string slackChannel = SlackAPI::SlackObjectToPlainText(args[5], true);
 

	
 
	std::string to = legacyRoom + "%" + legacyServer + "@" + m_component->getJID().toString();
 
	if (!CONFIG_BOOL_DEFAULTED(m_component->getConfig(), "registration.needRegistration", true)) {
 
		m_uinfo.uin = name;
 
		m_storageBackend->setUser(m_uinfo);
 
	}
 
// 	else {
 
// 		to =  legacyRoom + "\\40" + legacyServer + "@" + m_component->getJID().toString();
 
// 	}
 

	
 
	m_jid2channel[to] = slackChannel;
 
	m_channel2jid[slackChannel] = to;
 

	
 
	LOG4CXX_INFO(logger, "Setting transport between " << to << " and " << slackChannel);
 

	
 
	std::string rooms = "";
 
	int type = (int) TYPE_STRING;
 
	m_storageBackend->getUserSetting(m_uinfo.id, "rooms", type, rooms);
 
	rooms += message + "\n";
 
	m_storageBackend->updateUserSetting(m_uinfo.id, "rooms", rooms);
 
	if (!quiet) {
 
		std::string rooms = "";
 
		int type = (int) TYPE_STRING;
 
		m_storageBackend->getUserSetting(m_uinfo.id, "rooms", type, rooms);
 
		rooms += message + "\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 + "/" + name));
 
	presence->setType(Swift::Presence::Available);
 
	presence->addPayload(boost::shared_ptr<Swift::Payload>(new Swift::MUCPayload()));
 
	m_component->getFrontend()->onPresenceReceived(presence);
 

	
 
	m_onlineBuddiesTimer->start();
 

	
 
	if (!quiet) {
 
		std::string msg;
 
		msg += "Spectrum 2 is now joining the room. To leave the room later to disable transporting, you can use `.spectrum2 leave.room #" + SlackAPI::SlackObjectToPlainText(args[5], true, true) + "`.";
 
		m_rtm->sendMessage(m_ownerChannel, msg);
 
	}
 
}
 

	
 
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];
 
	if (to.empty()) {
 
@@ -191,30 +242,34 @@ void SlackSession::handleRegisterMessage(const std::string &message, std::vector
 
	m_component->getFrontend()->onPresenceReceived(presence);
 

	
 
	if (!quiet) {
 
		std::string msg;
 
		msg += "You have successfully registered 3rd-party account. Spectrum 2 is now connecting to the 3rd-party network.";
 
		m_rtm->sendMessage(m_ownerChannel, msg);
 
	}
 
}
 

	
 
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()) {
 
			return;
 
		}
 

	
 
		if (!to.empty()) {
 
			boost::shared_ptr<Swift::Message> msg(new Swift::Message());
 
			msg->setType(Swift::Message::Groupchat);
 
			msg->setTo(to);
 
			msg->setFrom(Swift::JID("", m_uinfo.jid, "default"));
 
			msg->setBody("<" + user + "> " + message);
 
			msg->setBody("<" + m_rtm->getUserName(user) + "> " + message);
 
			m_component->getFrontend()->onMessageReceived(msg);
 
		}
 
		else {
 
			// When changing the purpose, we do not want to spam to room with the info,
 
			// so remove the purpose message.
 
// 			if (message.find("set the channel purpose") != std::string::npos) {
 
// 				m_rtm->getAPI()->deleteMessage(channel, ts);
 
// 			}
 
			// TODO: MAP `user` to JID somehow and send the message to proper JID.
 
			// So far send to all online contacts
 

	
 
			if (!m_user || !m_user->getRosterManager()) {
 
@@ -232,25 +287,25 @@ void SlackSession::handleMessageReceived(const std::string &channel, const std::
 

	
 
				if (!(b->getStatus(s, statusMessage))) {
 
					continue;
 
				}
 

	
 
				if (s.getType() == Swift::StatusShow::None) {
 
					continue;
 
				}
 

	
 
				boost::shared_ptr<Swift::Message> msg(new Swift::Message());
 
				msg->setTo(b->getJID());
 
				msg->setFrom(Swift::JID("", m_uinfo.jid, "default"));
 
				msg->setBody("<" + user + "> " + message);
 
				msg->setBody("<" + m_rtm->getUserName(user) + "> " + message);
 
				m_component->getFrontend()->onMessageReceived(msg);
 
			}
 
		}
 
		return;
 
	}
 

	
 
	std::vector<std::string> args;
 
	boost::split(args, message, boost::is_any_of(" "));
 

	
 
	if (args.size() < 2 || args[0] != ".spectrum2") {
 
		m_rtm->sendMessage(m_ownerChannel, "Unknown command. Use \".spectrum2 help\" for help.");
 
		return;
spectrum/src/frontends/slack/SlackSession.h
Show inline comments
 
@@ -19,63 +19,68 @@
 
 */
 

	
 
#pragma once
 

	
 
#include "transport/StorageBackend.h"
 
#include "rapidjson/document.h"
 

	
 
#include <string>
 
#include <algorithm>
 
#include <map>
 

	
 
#include "Swiften/Elements/Message.h"
 
#include "Swiften/Network/Timer.h"
 

	
 
#include <boost/signal.hpp>
 

	
 
namespace Transport {
 

	
 
class Component;
 
class StorageBackend;
 
class HTTPRequest;
 
class SlackRTM;
 
class SlackAPI;
 
class User;
 

	
 
class SlackSession {
 
	public:
 
		SlackSession(Component *component, StorageBackend *storageBackend, UserInfo uinfo);
 

	
 
		virtual ~SlackSession();
 

	
 
		boost::signal<void (const std::string &user)> onInstallationDone;
 

	
 
		void sendMessage(boost::shared_ptr<Swift::Message> message);
 

	
 
		void setPurpose(const std::string &purpose);
 
		void setPurpose(const std::string &purpose, const std::string &channel = "");
 

	
 
		void setUser(User *user) {
 
			m_user = user;
 
		}
 

	
 
	private:
 
		void handleRTMStarted();
 
		void handleMessageReceived(const std::string &channel, const std::string &user, const std::string &message, const std::string &ts, bool quiet);
 
		void handleImOpen(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data);
 

	
 
		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 handleRegisterMessage(const std::string &message, std::vector<std::string> &args, bool quiet = false);
 

	
 
		void sendOnlineBuddies();
 

	
 
	private:
 
		Component *m_component;
 
		StorageBackend *m_storageBackend;
 
		UserInfo m_uinfo;
 
		std::string m_ownerName;
 
		SlackRTM *m_rtm;
 
		std::string m_ownerChannel;
 
		std::map<std::string, std::string> m_jid2channel;
 
		std::map<std::string, std::string> m_channel2jid;
 
		std::string m_slackChannel;
 
		User *m_user;
 
		Swift::Timer::ref m_onlineBuddiesTimer;
 
		std::map<std::string, std::string> m_onlineBuddies;
 
};
 

	
 
}
src/Conversation.cpp
Show inline comments
 
@@ -195,24 +195,32 @@ void Conversation::handleMessage(boost::shared_ptr<Swift::Message> &message, con
 
		std::string n = nickname;
 
		if (n.empty()) {
 
			n = " ";
 
		}
 

	
 
		message->setFrom(Swift::JID(legacyName, m_conversationManager->getComponent()->getJID().toBare(), n));
 
		LOG4CXX_INFO(logger, "MSG FROM " << message->getFrom().toString());
 
	}
 

	
 
	handleRawMessage(message);
 
}
 

	
 
std::string Conversation::getParticipants() {
 
	std::string ret;
 
	for (std::map<std::string, Swift::Presence::ref>::iterator it = m_participants.begin(); it != m_participants.end(); it++) {
 
		ret += (*it).second->getFrom().getResource() + ", ";
 
	}
 
	return ret;
 
}
 

	
 
void Conversation::sendParticipants(const Swift::JID &to) {
 
	for (std::map<std::string, Swift::Presence::ref>::iterator it = m_participants.begin(); it != m_participants.end(); it++) {
 
		(*it).second->setTo(to);
 
		m_conversationManager->getComponent()->getFrontend()->sendPresence((*it).second);
 
	}
 
}
 

	
 
void Conversation::sendCachedMessages(const Swift::JID &to) {
 
	for (std::list<boost::shared_ptr<Swift::Message> >::const_iterator it = m_cachedMessages.begin(); it != m_cachedMessages.end(); it++) {
 
		if (to.isValid()) {
 
			(*it)->setTo(to);
 
		}
0 comments (0 inline, 0 general)