From 05bf03704aecaabfb24a96d84918b36a9108d4f0 Mon Sep 17 00:00:00 2001 From: Jan Kaluza Date: Wed, 25 Nov 2015 16:02:23 +0100 Subject: [PATCH] First half-working version of IRC transport for Slack --- spectrum/src/frontends/slack/SlackAPI.cpp | 8 +- spectrum/src/frontends/slack/SlackAPI.h | 1 + .../src/frontends/slack/SlackFrontend.cpp | 1 + .../src/frontends/slack/SlackInstallation.cpp | 80 ++++++++++++++++++- .../src/frontends/slack/SlackInstallation.h | 8 ++ spectrum/src/frontends/slack/SlackRTM.h | 4 + .../src/frontends/slack/SlackUserManager.cpp | 23 +++--- .../src/frontends/slack/SlackUserManager.h | 2 + .../frontends/slack/SlackUserRegistration.cpp | 46 +++++++---- src/Conversation.cpp | 4 + src/NetworkPluginServer.cpp | 5 +- src/SQLite3Backend.cpp | 15 ++-- 12 files changed, 162 insertions(+), 35 deletions(-) diff --git a/spectrum/src/frontends/slack/SlackAPI.cpp b/spectrum/src/frontends/slack/SlackAPI.cpp index 3b698225..7d387384 100644 --- a/spectrum/src/frontends/slack/SlackAPI.cpp +++ b/spectrum/src/frontends/slack/SlackAPI.cpp @@ -84,6 +84,12 @@ std::string SlackAPI::getChannelId(HTTPRequest *req, bool ok, rapidjson::Documen return id.GetString(); } +void SlackAPI::channelsCreate(const std::string &name, HTTPRequest::Callback callback) { + std::string url = "https://slack.com/api/channels.create?name=" + Util::urlencode(name) + "&token=" + Util::urlencode(m_token); + HTTPRequest *req = new HTTPRequest(THREAD_POOL(m_component), HTTPRequest::Get, url, callback); + queueRequest(req); +} + void SlackAPI::imOpen(const std::string &uid, HTTPRequest::Callback callback) { std::string url = "https://slack.com/api/im.open?user=" + Util::urlencode(uid) + "&token=" + Util::urlencode(m_token); HTTPRequest *req = new HTTPRequest(THREAD_POOL(m_component), HTTPRequest::Get, url, callback); @@ -183,7 +189,7 @@ void SlackAPI::getSlackChannelInfo(HTTPRequest *req, bool ok, rapidjson::Documen info.members.push_back(members[i].GetString()); } - ret[info.id] = info; + ret[info.name] = info; } return; diff --git a/spectrum/src/frontends/slack/SlackAPI.h b/spectrum/src/frontends/slack/SlackAPI.h index 039247d8..b5a77aaf 100644 --- a/spectrum/src/frontends/slack/SlackAPI.h +++ b/spectrum/src/frontends/slack/SlackAPI.h @@ -75,6 +75,7 @@ class SlackAPI : public HTTPRequestQueue { void usersList(HTTPRequest::Callback callback); std::string getOwnerId(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data); + void channelsCreate(const std::string &name, HTTPRequest::Callback callback); void imOpen(const std::string &uid, HTTPRequest::Callback callback); std::string getChannelId(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data); diff --git a/spectrum/src/frontends/slack/SlackFrontend.cpp b/spectrum/src/frontends/slack/SlackFrontend.cpp index 4af86579..d69f7fa4 100644 --- a/spectrum/src/frontends/slack/SlackFrontend.cpp +++ b/spectrum/src/frontends/slack/SlackFrontend.cpp @@ -78,6 +78,7 @@ void SlackFrontend::sendRosterRequest(Swift::RosterPayload::ref payload, Swift:: } void SlackFrontend::sendMessage(boost::shared_ptr message) { + return static_cast(m_userManager)->sendMessage(message); } void SlackFrontend::sendIQ(boost::shared_ptr iq) { diff --git a/spectrum/src/frontends/slack/SlackInstallation.cpp b/spectrum/src/frontends/slack/SlackInstallation.cpp index 2d67ca30..5ddd5eb9 100644 --- a/spectrum/src/frontends/slack/SlackInstallation.cpp +++ b/spectrum/src/frontends/slack/SlackInstallation.cpp @@ -26,9 +26,15 @@ #include "transport/Transport.h" #include "transport/HTTPRequest.h" #include "transport/Util.h" +#include "transport/Buddy.h" #include #include +#include +#include + +#include "Swiften/Elements/MUCPayload.h" + #include #include @@ -43,15 +49,85 @@ SlackInstallation::SlackInstallation(Component *component, StorageBackend *stora m_rtm = new SlackRTM(component, storageBackend, uinfo); m_rtm->onRTMStarted.connect(boost::bind(&SlackInstallation::handleRTMStarted, this)); m_rtm->onMessageReceived.connect(boost::bind(&SlackInstallation::handleMessageReceived, this, _1, _2, _3)); + +// m_api = new SlackAPI(component, m_uinfo.encoding); } SlackInstallation::~SlackInstallation() { delete m_rtm; +// delete m_api; +} + +void SlackInstallation::sendMessage(boost::shared_ptr message) { + LOG4CXX_INFO(logger, "SEND MESSAGE"); + if (message->getFrom().getResource() == "myfavouritebot") { + return; + } + m_rtm->getAPI()->sendMessage(message->getFrom().getResource(), m_jid2channel[message->getFrom().toBare().toString()], message->getBody()); } void SlackInstallation::handleMessageReceived(const std::string &channel, const std::string &user, const std::string &message) { - if (m_ownerChannel == channel) { - LOG4CXX_INFO(logger, "Owner message received " << channel << " " << user << " " << message); + if (m_ownerChannel != channel) { + std::string to = m_channel2jid[channel]; + if (!to.empty()) { + boost::shared_ptr msg(new Swift::Message()); + msg->setType(Swift::Message::Groupchat); + msg->setTo(to); + msg->setFrom(Swift::JID("", "spectrum2", "default")); + msg->setBody("<" + 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") { + // .spectrum2 join BotName #room irc.freenode.net channel + LOG4CXX_INFO(logger, "Received JOIN request" << args.size()); + if (args.size() == 6) { + LOG4CXX_INFO(logger, "Received JOIN request"); + if (args[4][0] == '<') { + args[4] = args[4].substr(1, args[4].size() - 2); + args[4] = args[4].substr(args[4].find("|") + 1); + LOG4CXX_INFO(logger, args[4]); + } + + if (args[5][0] == '<') { + args[5] = args[5].substr(2, args[5].size() - 3); + } + + + m_uinfo.uin = args[2]; + m_storageBackend->setUser(m_uinfo); + + + std::string to = args[3] + "%" + args[4] + "@localhost"; + m_jid2channel[to] = args[5]; + m_channel2jid[args[5]] = to; + LOG4CXX_INFO(logger, "Setting transport between " << to << " and " << args[5]); + // int type = (int) TYPE_STRING; + // m_storageBackend->getUserSetting(user.id, "room", type, to); + + Swift::Presence::ref presence = Swift::Presence::create(); + presence->setFrom(Swift::JID("", "spectrum2", "default")); + presence->setTo(Swift::JID(to + "/" + args[2])); + presence->setType(Swift::Presence::Available); + presence->addPayload(boost::shared_ptr(new Swift::MUCPayload())); + m_component->getFrontend()->onPresenceReceived(presence); + } + } + else if (args[1] == "help") { + + } + else { + m_rtm->sendMessage(m_ownerChannel, "Unknown command. Use \".spectrum2 help\" for help."); } } diff --git a/spectrum/src/frontends/slack/SlackInstallation.h b/spectrum/src/frontends/slack/SlackInstallation.h index a126547a..8f76cf13 100644 --- a/spectrum/src/frontends/slack/SlackInstallation.h +++ b/spectrum/src/frontends/slack/SlackInstallation.h @@ -27,6 +27,8 @@ #include #include +#include "Swiften/Elements/Message.h" + #include namespace Transport { @@ -35,6 +37,7 @@ class Component; class StorageBackend; class HTTPRequest; class SlackRTM; +class SlackAPI; class SlackInstallation { public: @@ -44,6 +47,8 @@ class SlackInstallation { boost::signal onInstallationDone; + void sendMessage(boost::shared_ptr message); + private: void handleRTMStarted(); void handleMessageReceived(const std::string &channel, const std::string &user, const std::string &message); @@ -56,6 +61,9 @@ class SlackInstallation { std::string m_ownerName; SlackRTM *m_rtm; std::string m_ownerChannel; + SlackAPI *m_api; + std::map m_jid2channel; + std::map m_channel2jid; }; } diff --git a/spectrum/src/frontends/slack/SlackRTM.h b/spectrum/src/frontends/slack/SlackRTM.h index 6e1b6d34..66332d6d 100644 --- a/spectrum/src/frontends/slack/SlackRTM.h +++ b/spectrum/src/frontends/slack/SlackRTM.h @@ -72,6 +72,10 @@ class SlackRTM { return m_users; } + std::map &getChannels() { + return m_channels; + } + SlackAPI *getAPI() { return m_api; } diff --git a/spectrum/src/frontends/slack/SlackUserManager.cpp b/spectrum/src/frontends/slack/SlackUserManager.cpp index 0276560b..20cee001 100644 --- a/spectrum/src/frontends/slack/SlackUserManager.cpp +++ b/spectrum/src/frontends/slack/SlackUserManager.cpp @@ -49,27 +49,32 @@ void SlackUserManager::reconnectUser(const std::string &user) { return; } - if (!uinfo.uin.empty()) { - LOG4CXX_INFO(logger, "Reconnecting user " << user); - Swift::Presence::ref response = Swift::Presence::create(); - response->setTo(m_component->getJID()); - response->setFrom(user + "@" + m_component->getJID().toString()); - response->setType(Swift::Presence::Available); - } - else { +// if (!uinfo.uin.empty()) { +// LOG4CXX_INFO(logger, "Reconnecting user " << user); +// Swift::Presence::ref response = Swift::Presence::create(); +// response->setTo(m_component->getJID()); +// response->setFrom(user + "@" + m_component->getJID().toString()); +// response->setType(Swift::Presence::Available); +// } +// else { LOG4CXX_INFO(logger, "Cannot reconnect user " << user << "," "because he does not have legacy network configured. " "Continuing in Installation mode for this user until " "he configures the legacy network."); m_installations[user] = new SlackInstallation(m_component, m_storageBackend, uinfo); m_installations[user]->onInstallationDone.connect(boost::bind(&SlackUserManager::reconnectUser, this, _1)); - } +// } } void SlackUserManager::sendVCard(unsigned int id, Swift::VCard::ref vcard) { } +void SlackUserManager::sendMessage(boost::shared_ptr message) { + LOG4CXX_INFO(logger, message->getTo().toBare().toString()); + m_installations[message->getTo().toBare().toString()]->sendMessage(message); +} + UserRegistration *SlackUserManager::getUserRegistration() { return m_userRegistration; diff --git a/spectrum/src/frontends/slack/SlackUserManager.h b/spectrum/src/frontends/slack/SlackUserManager.h index afc8b2c7..711379cd 100644 --- a/spectrum/src/frontends/slack/SlackUserManager.h +++ b/spectrum/src/frontends/slack/SlackUserManager.h @@ -57,6 +57,8 @@ class SlackUserManager : public UserManager { std::string getOAuth2URL(const std::vector &args); + void sendMessage(boost::shared_ptr message); + private: Component *m_component; UserRegistration *m_userRegistration; diff --git a/spectrum/src/frontends/slack/SlackUserRegistration.cpp b/spectrum/src/frontends/slack/SlackUserRegistration.cpp index 4f34df74..a8a9897e 100644 --- a/spectrum/src/frontends/slack/SlackUserRegistration.cpp +++ b/spectrum/src/frontends/slack/SlackUserRegistration.cpp @@ -104,19 +104,26 @@ std::string SlackUserRegistration::getTeamDomain(const std::string &token) { std::string SlackUserRegistration::handleOAuth2Code(const std::string &code, const std::string &state) { OAuth2 *oauth2 = NULL; + std::string token; std::vector data; - if (m_auths.find(state) != m_auths.end()) { - oauth2 = m_auths[state]; - data = m_authsData[state]; + + if (state == "use_bot_token") { + token = code; } else { - return "Received state code '" + state + "' not found in state codes list."; - } + if (m_auths.find(state) != m_auths.end()) { + oauth2 = m_auths[state]; + data = m_authsData[state]; + } + else { + return "Received state code '" + state + "' not found in state codes list."; + } - std::string token; - std::string error = oauth2->requestToken(code, token); - if (!error.empty()) { - return error; + std::string token; + std::string error = oauth2->requestToken(code, token); + if (!error.empty()) { + return error; + } } UserInfo user; @@ -131,16 +138,25 @@ std::string SlackUserRegistration::handleOAuth2Code(const std::string &code, con m_storageBackend->getUser(user.jid, user); - std::string value = data[2]; - int type = (int) TYPE_STRING; - m_storageBackend->getUserSetting(user.id, "bot_token", type, value); + if (oauth2) { + std::string value = data[2]; + int type = (int) TYPE_STRING; + m_storageBackend->getUserSetting(user.id, "bot_token", type, value); + } + else { + std::string value = token; + int type = (int) TYPE_STRING; + m_storageBackend->getUserSetting(user.id, "bot_token", type, value); + } LOG4CXX_INFO(logger, "Registered Slack user " << user.jid); - m_auths.erase(state); - delete oauth2; + if (oauth2) { + m_auths.erase(state); + delete oauth2; - m_authsData.erase(state); + m_authsData.erase(state); + } m_component->getFrontend()->reconnectUser(user.jid); diff --git a/src/Conversation.cpp b/src/Conversation.cpp index de727440..3a717487 100644 --- a/src/Conversation.cpp +++ b/src/Conversation.cpp @@ -27,6 +27,7 @@ #include "transport/RosterManager.h" #include "transport/Frontend.h" #include "transport/Config.h" +#include "transport/Logging.h" #include "Swiften/Elements/MUCItem.h" #include "Swiften/Elements/MUCOccupant.h" @@ -35,6 +36,8 @@ #include "Swiften/Elements/MUCPayload.h" namespace Transport { + +DEFINE_LOGGER(logger, "Conversation"); Conversation::Conversation(ConversationManager *conversationManager, const std::string &legacyName, bool isMUC) : m_conversationManager(conversationManager) { m_legacyName = legacyName; @@ -195,6 +198,7 @@ void Conversation::handleMessage(boost::shared_ptr &message, con } message->setFrom(Swift::JID(legacyName, m_conversationManager->getComponent()->getJID().toBare(), n)); + LOG4CXX_INFO(logger, "MSG FROM " << message->getFrom().toString()); } handleRawMessage(message); diff --git a/src/NetworkPluginServer.cpp b/src/NetworkPluginServer.cpp index 5f8ec2e8..2e6a49ce 100644 --- a/src/NetworkPluginServer.cpp +++ b/src/NetworkPluginServer.cpp @@ -661,8 +661,10 @@ void NetworkPluginServer::handleConvMessagePayload(const std::string &data, bool } User *user = m_userManager->getUser(payload.username()); - if (!user) + if (!user) { + LOG4CXX_ERROR(logger, "handleConvMessagePayload: unknown username " << payload.username()); return; + } // Message from legacy network triggers network acticity user->updateLastActivity(); @@ -697,6 +699,7 @@ void NetworkPluginServer::handleConvMessagePayload(const std::string &data, bool // We can't create Conversation for payload with nickname, because this means the message is from room, // but this user is not in any room, so it's OK to just reject this message if (!conv && !payload.nickname().empty()) { + LOG4CXX_WARN(logger, "handleConvMessagePayload: No conversation with name " << payload.buddyname()); return; } diff --git a/src/SQLite3Backend.cpp b/src/SQLite3Backend.cpp index 6a1b907e..5567dcc8 100644 --- a/src/SQLite3Backend.cpp +++ b/src/SQLite3Backend.cpp @@ -120,7 +120,7 @@ bool SQLite3Backend::connect() { if (createDatabase() == false) return false; - PREP_STMT(m_setUser, "INSERT OR REPLACE INTO " + m_prefix + "users (jid, uin, password, language, encoding, last_login, vip) VALUES (?, ?, ?, ?, ?, DATETIME('NOW'), ?)"); + PREP_STMT(m_setUser, "INSERT OR REPLACE INTO " + m_prefix + "users (id, jid, uin, password, language, encoding, last_login, vip) VALUES (?, ?, ?, ?, ?, ?, DATETIME('NOW'), ?)"); PREP_STMT(m_getUser, "SELECT id, jid, uin, password, encoding, language, vip FROM " + m_prefix + "users WHERE jid=?"); PREP_STMT(m_removeUser, "DELETE FROM " + m_prefix + "users WHERE id=?"); @@ -223,12 +223,13 @@ bool SQLite3Backend::exec(const std::string &query) { void SQLite3Backend::setUser(const UserInfo &user) { sqlite3_reset(m_setUser); - sqlite3_bind_text(m_setUser, 1, user.jid.c_str(), -1, SQLITE_STATIC); - sqlite3_bind_text(m_setUser, 2, user.uin.c_str(), -1, SQLITE_STATIC); - sqlite3_bind_text(m_setUser, 3, user.password.c_str(), -1, SQLITE_STATIC); - sqlite3_bind_text(m_setUser, 4, user.language.c_str(), -1, SQLITE_STATIC); - sqlite3_bind_text(m_setUser, 5, user.encoding.c_str(), -1, SQLITE_STATIC); - sqlite3_bind_int (m_setUser, 6, user.vip); + sqlite3_bind_int(m_setUser, 1, user.id); + sqlite3_bind_text(m_setUser, 2, user.jid.c_str(), -1, SQLITE_STATIC); + sqlite3_bind_text(m_setUser, 3, user.uin.c_str(), -1, SQLITE_STATIC); + sqlite3_bind_text(m_setUser, 4, user.password.c_str(), -1, SQLITE_STATIC); + sqlite3_bind_text(m_setUser, 5, user.language.c_str(), -1, SQLITE_STATIC); + sqlite3_bind_text(m_setUser, 6, user.encoding.c_str(), -1, SQLITE_STATIC); + sqlite3_bind_int (m_setUser, 7, user.vip); if(sqlite3_step(m_setUser) != SQLITE_DONE) { LOG4CXX_ERROR(logger, "setUser query"<< (sqlite3_errmsg(m_db) == NULL ? "" : sqlite3_errmsg(m_db)));