/** * XMPP - libpurple transport * * Copyright (C) 2009, Jan Kaluza * * 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 "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 #include #include #include #include "Swiften/Elements/MUCPayload.h" #include #include namespace Transport { DEFINE_LOGGER(logger, "SlackSession"); SlackSession::SlackSession(Component *component, StorageBackend *storageBackend, UserInfo uinfo) : m_uinfo(uinfo), m_user(NULL), m_disconnected(false) { 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)); 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); } SlackSession::~SlackSession() { delete m_rtm; delete m_api; m_onlineBuddiesTimer->stop(); } void SlackSession::sendOnlineBuddies() { if (!m_user) { return; } std::map convs = m_user->getConversationManager()->getConversations(); for (std::map ::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::sendMessageToAll(const std::string &msg) { std::vector channels; for (std::map::const_iterator it = m_jid2channel.begin(); it != m_jid2channel.end(); it++) { if (std::find(channels.begin(), channels.end(), it->second) == channels.end()) { channels.push_back(it->second); m_rtm->getAPI()->sendMessage("Soectrum 2", it->second, msg); } } } void SlackSession::sendMessage(boost::shared_ptr message) { if (m_user) { std::map convs = m_user->getConversationManager()->getConversations(); for (std::map ::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() << " channel is " << channel); 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(), m_user); 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, const std::string &channel) { std::string ch = channel; if (ch.empty()) { ch = m_slackChannel; } if (ch.empty()) { return; } LOG4CXX_INFO(logger, "Setting channel purppose: " << ch << " " << purpose); m_api->setPurpose(ch, purpose); } void SlackSession::joinRoom(std::vector args) { std::string &name = args[2]; std::string &legacyRoom = args[3]; std::string &legacyServer = args[4]; std::string &slackChannel = args[5]; 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; 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(new Swift::MUCPayload())); m_component->getFrontend()->onPresenceReceived(presence); m_onlineBuddiesTimer->start(); } void SlackSession::handleJoinRoomCreate(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data, std::vector args) { std::string channelId = m_api->getChannelId(req, ok, resp, data); if (channelId.empty()) { LOG4CXX_INFO(logger, args[1] << ": Error creating channel " << args[5] << "."); return; } args[5] = channelId; joinRoom(args); } void SlackSession::handleJoinRoomList(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data, std::vector args) { std::map channels; SlackAPI::getSlackChannelInfo(req, ok, resp, data, channels); if (channels.find(args[5]) != channels.end()) { LOG4CXX_INFO(logger, args[1] << ": Channel " << args[5] << " already exists. Joining the room."); args[5] = channels[args[5]].id; joinRoom(args); } else { LOG4CXX_INFO(logger, args[1] << ": Channel " << args[5] << " does not exit. Creating it."); m_api->channelsCreate(args[5], boost::bind(&SlackSession::handleJoinRoomCreate, this, _1, _2, _3, _4, args)); } } void SlackSession::handleJoinMessage(const std::string &message, std::vector &args, bool quiet) { LOG4CXX_INFO(logger, args[1] << ": Going to join the room, checking the ID of channel " << args[5]); m_api->channelsList(boost::bind(&SlackSession::handleJoinRoomList, this, _1, _2, _3, _4, args)); } void SlackSession::handleSlackChannelInvite(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data) { Swift::Presence::ref presence = Swift::Presence::create(); presence->setFrom(Swift::JID("", m_uinfo.jid, "default")); presence->setTo(m_component->getJID()); presence->setType(Swift::Presence::Available); presence->addPayload(boost::shared_ptr(new Swift::MUCPayload())); m_component->getFrontend()->onPresenceReceived(presence); } void SlackSession::handleSlackChannelCreate(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data) { std::string channelId = m_api->getChannelId(req, ok, resp, data); if (channelId.empty()) { LOG4CXX_INFO(logger,"Error creating channel " << m_slackChannel << "."); return; } m_slackChannel = channelId; m_api->channelsInvite(m_slackChannel, m_rtm->getSelfId(), boost::bind(&SlackSession::handleSlackChannelInvite, this, _1, _2, _3, _4)); } void SlackSession::handleSlackChannelList(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data) { std::map channels; SlackAPI::getSlackChannelInfo(req, ok, resp, data, channels); if (channels.find(m_slackChannel) != channels.end()) { m_slackChannel = channels[m_slackChannel].id; m_api->channelsInvite(m_slackChannel, m_rtm->getSelfId(), boost::bind(&SlackSession::handleSlackChannelInvite, this, _1, _2, _3, _4)); } else { m_api->channelsCreate(m_slackChannel, boost::bind(&SlackSession::handleSlackChannelCreate, this, _1, _2, _3, _4)); } } void SlackSession::handleLeaveMessage(const std::string &message, std::vector &args, bool quiet) { // .spectrum2 leave.room channel std::string slackChannel = SlackAPI::SlackObjectToPlainText(args[2], true); std::string to = m_channel2jid[slackChannel]; 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 commands; boost::split(commands, rooms, boost::is_any_of("\n")); rooms = ""; BOOST_FOREACH(const std::string &command, commands) { if (command.size() > 5) { std::vector 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)); presence->setType(Swift::Presence::Unavailable); presence->addPayload(boost::shared_ptr(new Swift::MUCPayload())); m_component->getFrontend()->onPresenceReceived(presence); } void SlackSession::handleRegisterMessage(const std::string &message, std::vector &args, bool quiet) { // .spectrum2 register test@xmpp.tld mypassword #slack_channel m_uinfo.uin = SlackAPI::SlackObjectToPlainText(args[2]); m_uinfo.password = args[3]; std::string slackChannel = SlackAPI::SlackObjectToPlainText(args[4], true); m_storageBackend->setUser(m_uinfo); int type = (int) TYPE_STRING; m_storageBackend->getUserSetting(m_uinfo.id, "slack_channel", type, slackChannel); Swift::Presence::ref presence = Swift::Presence::create(); presence->setFrom(Swift::JID("", m_uinfo.jid, "default")); presence->setTo(m_component->getJID()); presence->setType(Swift::Presence::Available); presence->addPayload(boost::shared_ptr(new Swift::MUCPayload())); 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 msg(new Swift::Message()); msg->setType(Swift::Message::Groupchat); msg->setTo(to); msg->setFrom(Swift::JID("", m_uinfo.jid, "default")); 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()) { return; } Swift::StatusShow s; std::string statusMessage; const RosterManager::BuddiesMap &roster = m_user->getRosterManager()->getBuddies(); for(RosterManager::BuddiesMap::const_iterator bt = roster.begin(); bt != roster.end(); bt++) { Buddy *b = (*bt).second; if (!b) { continue; } if (!(b->getStatus(s, statusMessage))) { continue; } if (s.getType() == Swift::StatusShow::None) { continue; } boost::shared_ptr msg(new Swift::Message()); msg->setTo(b->getJID()); msg->setFrom(Swift::JID("", m_uinfo.jid, "default")); msg->setBody("<" + m_rtm->getUserName(user) + "> " + message); m_component->getFrontend()->onMessageReceived(msg); } } return; } std::vector 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; } if (args[1] == "join.room" && args.size() == 6) { handleJoinMessage(message, args, quiet); } else if (args[1] == "leave.room" && args.size() == 3) { handleLeaveMessage(message, args, quiet); } else if (args[1] == "register" && args.size() == 5) { handleRegisterMessage(message, args, quiet); } else if (args[1] == "set_main_channel" && args.size() == 3) { std::string slackChannel = SlackAPI::SlackObjectToPlainText(args[2], true); m_storageBackend->setUser(m_uinfo); int type = (int) TYPE_STRING; m_storageBackend->getUserSetting(m_uinfo.id, "slack_channel", type, slackChannel); Swift::Presence::ref presence = Swift::Presence::create(); presence->setFrom(Swift::JID("", m_uinfo.jid, "default")); presence->setTo(m_component->getJID()); presence->setType(Swift::Presence::Available); presence->addPayload(boost::shared_ptr(new Swift::MUCPayload())); m_component->getFrontend()->onPresenceReceived(presence); } else if (args[1] == "list.rooms" && args.size() == 2) { // .spectrum2 list.rooms std::string rooms = ""; int type = (int) TYPE_STRING; m_storageBackend->getUserSetting(m_uinfo.id, "rooms", type, rooms); std::string msg = "Spectrum 2 is configured for following channels:\\n"; msg += "```" + rooms + "```"; m_rtm->sendMessage(m_ownerChannel, msg); } else if (args[1] == "reconnect") { Swift::Presence::ref presence = Swift::Presence::create(); presence->setFrom(Swift::JID("", m_uinfo.jid, "default")); presence->setTo(m_component->getJID()); presence->setType(Swift::Presence::Available); presence->addPayload(boost::shared_ptr(new Swift::MUCPayload())); m_component->getFrontend()->onPresenceReceived(presence); } else if (args[1] == "help") { std::string msg; msg = "Following commands are supported:\\n"; msg += "```.spectrum2 help``` Shows this help message.\\n"; msg += "```.spectrum2 join.room <3rdPartyBotName> <#3rdPartyRoom> <3rdPartyServer> <#SlackChannel>``` Starts transport between 3rd-party room and Slack channel."; msg += "```.spectrum2 leave.room <#SlackChannel>``` Leaves the 3rd-party room connected with the given Slack channel."; msg += "```.spectrum2 list.rooms``` List all the transported rooms."; msg += "```.spectrum2 reconnect``` Reconnects to 3rd-party network manually in case of fatal error."; msg += "```.spectrum2 register <3rdPartyNetworkAccount> <3rdPartyPassword> <#SlackChannel>``` Registers 3rd-party account for transportation."; m_rtm->sendMessage(m_ownerChannel, msg); } // else { // m_rtm->sendMessage(m_ownerChannel, "Unknown command. Use `.spectrum2 help` for help."); // } } void SlackSession::handleDisconnected() { m_disconnected = true; } void SlackSession::setUser(User *user) { m_user = user; } void SlackSession::handleConnected() { if (m_disconnected) { m_rtm->getAPI()->imOpen(m_ownerId, boost::bind(&SlackSession::handleImOpen, this, _1, _2, _3, _4)); m_disconnected = false; } } void SlackSession::handleImOpen(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data) { m_ownerChannel = m_rtm->getAPI()->getChannelId(req, ok, resp, data); LOG4CXX_INFO(logger, "Opened channel with team owner: " << m_ownerChannel); std::string rooms = ""; int type = (int) TYPE_STRING; m_storageBackend->getUserSetting(m_uinfo.id, "rooms", type, rooms); if (!CONFIG_BOOL_DEFAULTED(m_component->getConfig(), "registration.needRegistration", true)) { if (rooms.empty()) { std::string msg; msg = "Hi, it seems you have enabled Spectrum 2 transport for your Team. As a Team owner, you should now configure it:\\n"; msg += "1. At first, create new channel in which you want this Spectrum 2 transport to send the messages, or choose the existing one.\\n"; msg += "2. Invite this Spectrum 2 bot into this channel.\\n"; msg += "3. Configure the transportation between 3rd-party network and this channel by executing following command in this chat:\\n"; msg += "```.spectrum2 join.room NameOfYourBotIn3rdPartyNetwork #3rdPartyRoom hostname_of_3rd_party_server #SlackChannel```\\n"; msg += "For example to join #test123 channel on Freenode IRC server as MyBot and transport it into #slack_channel, the command would look like this:\\n"; msg += "```.spectrum2 join.room MyBot #test123 adams.freenode.net #slack_channel```\\n"; msg += "To get full list of available commands, executa `.spectrum2 help`\\n"; m_rtm->sendMessage(m_ownerChannel, msg); } } else { if (m_uinfo.uin.empty()) { std::string msg; msg = "Hi, it seems you have enabled Spectrum 2 transport for your Team. As a Team owner, you should now configure it:\\n"; msg += "1. At first, create new channel in which you want this Spectrum 2 transport to send the messages, or choose the existing one.\\n"; msg += "2. Invite this Spectrum 2 bot into this channel.\\n"; msg += "3. Configure the transportation between 3rd-party network and this channel by executing following command in this chat:\\n"; msg += "```.spectrum2 register 3rdPartyAccount 3rdPartyPassword #SlackChannel```\\n"; msg += "For example to join XMPP account test@xmpp.tld and transport it into #slack_channel, the command would look like this:\\n"; msg += "```.spectrum2 register test@xmpp.tld mypassword #slack_channel```\\n"; msg += "To get full list of available commands, executa `.spectrum2 help`\\n"; m_rtm->sendMessage(m_ownerChannel, msg); } else { m_storageBackend->getUserSetting(m_uinfo.id, "slack_channel", type, m_slackChannel); if (!m_slackChannel.empty()) { m_api->channelsList(boost::bind(&SlackSession::handleSlackChannelList, this, _1, _2, _3, _4)); } else { std::string msg; msg = "Hi, it seems you have enabled Spectrum 2 transport for your Team. As a Team owner, you should now configure it:\\n"; msg += "1. At first, create new channel in which you want this Spectrum 2 transport to send the messages, or choose the existing one.\\n"; msg += "2. Invite this Spectrum 2 bot into this channel.\\n"; msg += "3. Configure the transportation between 3rd-party network and this channel by executing following command in this chat:\\n"; msg += "```.spectrum2 set_main_channel #SlackChannel```\\n"; msg += "To get full list of available commands, executa `.spectrum2 help`\\n"; m_rtm->sendMessage(m_ownerChannel, msg); } } } // Auto-join the rooms configured by the Slack channel owner. if (!rooms.empty()) { std::vector commands; boost::split(commands, rooms, boost::is_any_of("\n")); BOOST_FOREACH(const std::string &command, commands) { if (command.size() > 5) { LOG4CXX_INFO(logger, m_uinfo.jid << ": Sending command from storage: " << command); handleMessageReceived(m_ownerChannel, "owner", command, "", true); } } } } void SlackSession::handleRTMStarted() { std::map &users = m_rtm->getUsers(); for (std::map::iterator it = users.begin(); it != users.end(); it++) { SlackUserInfo &info = it->second; if (info.isPrimaryOwner) { m_ownerId = it->first; break; } } m_rtm->getAPI()->imOpen(m_ownerId, boost::bind(&SlackSession::handleImOpen, this, _1, _2, _3, _4)); } }