Changeset - 00c5273fbb13
[Not reviewed]
0 6 0
Jan Kaluza - 9 years ago 2016-02-20 07:23:07
jkaluza@redhat.com
Slack: Handle channels starting with hash, do not reconnect to Slack RTM when URL expired
6 files changed with 43 insertions and 3 deletions:
0 comments (0 inline, 0 general)
backends/libpurple/main.cpp
Show inline comments
 
@@ -553,1608 +553,1611 @@ class SpectrumNetworkPlugin : public NetworkPlugin {
 
				bool support_get_info = prpl_info && prpl_info->get_info;
 

	
 
				if (!support_get_info || (CONFIG_BOOL(config, "backend.no_vcard_fetch") && name != purple_account_get_username_wrapped(account))) {
 
					PurpleNotifyUserInfo *user_info = purple_notify_user_info_new_wrapped();
 
					notify_user_info(purple_account_get_connection_wrapped(account), name.c_str(), user_info);
 
					purple_notify_user_info_destroy_wrapped(user_info);
 
				}
 
				else {
 
					serv_get_info_wrapped(purple_account_get_connection_wrapped(account), name.c_str());
 
				}
 
			}
 
		}
 

	
 
		void handleVCardUpdatedRequest(const std::string &user, const std::string &image, const std::string &nickname) {
 
			PurpleAccount *account = m_sessions[user];
 
			if (account) {
 
				purple_account_set_alias_wrapped(account, nickname.c_str());
 
#if PURPLE_MAJOR_VERSION >= 2 && PURPLE_MINOR_VERSION >= 7
 
				purple_account_set_public_alias_wrapped(account, nickname.c_str(), NULL, NULL);
 
#endif
 
				gssize size = image.size();
 
				// this will be freed by libpurple
 
				guchar *photo = (guchar *) g_malloc(size * sizeof(guchar));
 
				memcpy(photo, image.c_str(), size);
 

	
 
				if (!photo)
 
					return;
 
				purple_buddy_icons_set_account_icon_wrapped(account, photo, size);
 
			}
 
		}
 

	
 
		void handleBuddyRemovedRequest(const std::string &user, const std::string &buddyName, const std::vector<std::string> &groups) {
 
			PurpleAccount *account = m_sessions[user];
 
			if (account) {
 
				if (m_authRequests.find(user + buddyName) != m_authRequests.end()) {
 
					m_authRequests[user + buddyName]->deny_cb(m_authRequests[user + buddyName]->user_data);
 
					m_authRequests.erase(user + buddyName);
 
				}
 
				PurpleBuddy *buddy = purple_find_buddy_wrapped(account, buddyName.c_str());
 
				if (buddy) {
 
					purple_account_remove_buddy_wrapped(account, buddy, purple_buddy_get_group_wrapped(buddy));
 
					purple_blist_remove_buddy_wrapped(buddy);
 
				}
 
			}
 
		}
 

	
 
		void handleBuddyUpdatedRequest(const std::string &user, const std::string &buddyName, const std::string &alias, const std::vector<std::string> &groups_) {
 
			PurpleAccount *account = m_sessions[user];
 
			if (account) {
 
				std::string groups = groups_.empty() ? "" : groups_[0];
 

	
 
				if (m_authRequests.find(user + buddyName) != m_authRequests.end()) {
 
					m_authRequests[user + buddyName]->authorize_cb(m_authRequests[user + buddyName]->user_data);
 
					m_authRequests.erase(user + buddyName);
 
				}
 

	
 
				PurpleBuddy *buddy = purple_find_buddy_wrapped(account, buddyName.c_str());
 
				if (buddy) {
 
					if (getAlias(buddy) != alias) {
 
						purple_blist_alias_buddy_wrapped(buddy, alias.c_str());
 
						purple_blist_server_alias_buddy_wrapped(buddy, alias.c_str());
 
						serv_alias_buddy_wrapped(buddy);
 
					}
 

	
 
					PurpleGroup *group = purple_find_group_wrapped(groups.c_str());
 
					if (!group) {
 
						group = purple_group_new_wrapped(groups.c_str());
 
					}
 
					purple_blist_add_contact_wrapped(purple_buddy_get_contact_wrapped(buddy), group ,NULL);
 
				}
 
				else {
 
					PurpleBuddy *buddy = purple_buddy_new_wrapped(account, buddyName.c_str(), alias.c_str());
 

	
 
					// Add newly created buddy to legacy network roster.
 
					PurpleGroup *group = purple_find_group_wrapped(groups.c_str());
 
					if (!group) {
 
						group = purple_group_new_wrapped(groups.c_str());
 
					}
 
					purple_blist_add_buddy_wrapped(buddy, NULL, group ,NULL);
 
					purple_account_add_buddy_wrapped(account, buddy);
 
					LOG4CXX_INFO(logger, "Adding new buddy " << buddyName.c_str() << " to legacy network roster");
 
				}
 
			}
 
		}
 

	
 
		void handleBuddyBlockToggled(const std::string &user, const std::string &buddyName, bool blocked) {
 
			if (CONFIG_BOOL(config, "service.enable_privacy_lists")) {
 
				PurpleAccount *account = m_sessions[user];
 
				if (account) {
 
					if (blocked) {
 
						purple_privacy_deny_wrapped(account, buddyName.c_str(), FALSE, FALSE);
 
					}
 
					else {
 
						purple_privacy_allow_wrapped(account, buddyName.c_str(), FALSE, FALSE);
 
					}
 
				}
 
			}
 
		}
 

	
 
		void updateConversationActivity(PurpleAccount *account, const std::string &buddyName) {
 
			PurpleConversation *conv = purple_find_conversation_with_account_wrapped(PURPLE_CONV_TYPE_CHAT, buddyName.c_str(), account);
 
			if (!conv) {
 
				conv = purple_find_conversation_with_account_wrapped(PURPLE_CONV_TYPE_IM, buddyName.c_str(), account);
 
			}
 
			if (conv) {
 
				purple_conversation_set_data_wrapped(conv, "unseen_count", 0);
 
				purple_conversation_update_wrapped(conv, PURPLE_CONV_UPDATE_UNSEEN);
 
			}
 
		}
 

	
 
		void handleTypingRequest(const std::string &user, const std::string &buddyName) {
 
			PurpleAccount *account = m_sessions[user];
 
			if (account) {
 
				LOG4CXX_INFO(logger, user << ": sending typing notify to " << buddyName);
 
				serv_send_typing_wrapped(purple_account_get_connection_wrapped(account), buddyName.c_str(), PURPLE_TYPING);
 
				updateConversationActivity(account, buddyName);
 
			}
 
		}
 

	
 
		void handleTypedRequest(const std::string &user, const std::string &buddyName) {
 
			PurpleAccount *account = m_sessions[user];
 
			if (account) {
 
				serv_send_typing_wrapped(purple_account_get_connection_wrapped(account), buddyName.c_str(), PURPLE_TYPED);
 
				updateConversationActivity(account, buddyName);
 
			}
 
		}
 

	
 
		void handleStoppedTypingRequest(const std::string &user, const std::string &buddyName) {
 
			PurpleAccount *account = m_sessions[user];
 
			if (account) {
 
				serv_send_typing_wrapped(purple_account_get_connection_wrapped(account), buddyName.c_str(), PURPLE_NOT_TYPING);
 
				updateConversationActivity(account, buddyName);
 
			}
 
		}
 

	
 
		void handleAttentionRequest(const std::string &user, const std::string &buddyName, const std::string &message) {
 
			PurpleAccount *account = m_sessions[user];
 
			if (account) {
 
				purple_prpl_send_attention_wrapped(purple_account_get_connection_wrapped(account), buddyName.c_str(), 0);
 
			}
 
		}
 

	
 
		void handleJoinRoomRequest(const std::string &user, const std::string &room, const std::string &nickname, const std::string &pasword) {
 
			PurpleAccount *account = m_sessions[user];
 
			if (!account) {
 
				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) {
 
				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());
 
				}
 
			}
 

	
 
			if (CONFIG_STRING(config, "service.protocol") != "prpl-jabber") {
 
				np->handleParticipantChanged(np->m_accounts[account], nickname, room, 0, pbnetwork::STATUS_ONLINE);
 
				const char *disp;
 
				if ((disp = purple_connection_get_display_name(account->gc)) == NULL) {
 
					disp = purple_account_get_username(account);
 
				}
 

	
 
				LOG4CXX_INFO(logger, user << ": Display name is " << disp << ", nickname is " << nickname);
 
				if (nickname != disp) {
 
					handleRoomNicknameChanged(np->m_accounts[account], room, disp);
 
					np->handleParticipantChanged(np->m_accounts[account], nickname, room, 0, pbnetwork::STATUS_ONLINE, "", disp);
 
				}
 
			}
 

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

	
 
			PurpleConversation *conv = purple_find_conversation_with_account_wrapped(PURPLE_CONV_TYPE_CHAT, room.c_str(), account);
 
			purple_conversation_destroy_wrapped(conv);
 
		}
 

	
 
		void handleFTStartRequest(const std::string &user, const std::string &buddyName, const std::string &fileName, unsigned long size, unsigned long ftID) {
 
			PurpleXfer *xfer = m_unhandledXfers[user + fileName + buddyName];
 
			if (xfer) {
 
				m_unhandledXfers.erase(user + fileName + buddyName);
 
				FTData *ftData = (FTData *) xfer->ui_data;
 
				
 
				ftData->id = ftID;
 
				m_xfers[ftID] = xfer;
 
				purple_xfer_request_accepted_wrapped(xfer, fileName.c_str());
 
				purple_xfer_ui_ready_wrapped(xfer);
 
			}
 
		}
 

	
 
		void handleFTFinishRequest(const std::string &user, const std::string &buddyName, const std::string &fileName, unsigned long size, unsigned long ftID) {
 
			PurpleXfer *xfer = m_unhandledXfers[user + fileName + buddyName];
 
			if (xfer) {
 
				m_unhandledXfers.erase(user + fileName + buddyName);
 
				purple_xfer_request_denied_wrapped(xfer);
 
			}
 
		}
 

	
 
		void handleFTPauseRequest(unsigned long ftID) {
 
			PurpleXfer *xfer = m_xfers[ftID];
 
			if (!xfer)
 
				return;
 
			FTData *ftData = (FTData *) xfer->ui_data;
 
			ftData->paused = true;
 
		}
 

	
 
		void handleFTContinueRequest(unsigned long ftID) {
 
			PurpleXfer *xfer = m_xfers[ftID];
 
			if (!xfer)
 
				return;
 
			FTData *ftData = (FTData *) xfer->ui_data;
 
			ftData->paused = false;
 
			purple_xfer_ui_ready_wrapped(xfer);
 
		}
 

	
 
		void sendData(const std::string &string) {
 
#ifdef WIN32
 
			::send(main_socket, string.c_str(), string.size(), 0);
 
#else
 
			write(main_socket, string.c_str(), string.size());
 
#endif
 
			if (writeInput == 0)
 
				writeInput = purple_input_add_wrapped(main_socket, PURPLE_INPUT_WRITE, &transportDataReceived, NULL);
 
		}
 

	
 
		void readyForData() {
 
			if (m_waitingXfers.empty())
 
				return;
 
			std::vector<PurpleXfer *> tmp;
 
			tmp.swap(m_waitingXfers);
 

	
 
			for (std::vector<PurpleXfer *>::const_iterator it = tmp.begin(); it != tmp.end(); it++) {
 
				FTData *ftData = (FTData *) (*it)->ui_data;
 
				if (ftData->timer == 0) {
 
					ftData->timer = purple_timeout_add_wrapped(1, ft_ui_ready, *it);
 
				}
 
// 				purple_xfer_ui_ready_wrapped(xfer);
 
			}
 
		}
 

	
 
		std::map<std::string, PurpleAccount *> m_sessions;
 
		std::map<PurpleAccount *, std::string> m_accounts;
 
		std::map<std::string, unsigned int> m_vcards;
 
		std::map<std::string, authRequest *> m_authRequests;
 
		std::map<unsigned long, PurpleXfer *> m_xfers;
 
		std::map<std::string, PurpleXfer *> m_unhandledXfers;
 
		std::vector<PurpleXfer *> m_waitingXfers;
 
};
 

	
 
static bool getStatus(PurpleBuddy *m_buddy, pbnetwork::StatusType &status, std::string &statusMessage) {
 
	PurplePresence *pres = purple_buddy_get_presence_wrapped(m_buddy);
 
	if (pres == NULL)
 
		return false;
 
	PurpleStatus *stat = purple_presence_get_active_status_wrapped(pres);
 
	if (stat == NULL)
 
		return false;
 
	int st = purple_status_type_get_primitive_wrapped(purple_status_get_type_wrapped(stat));
 

	
 
	switch(st) {
 
		case PURPLE_STATUS_AVAILABLE: {
 
			status = pbnetwork::STATUS_ONLINE;
 
			break;
 
		}
 
		case PURPLE_STATUS_AWAY: {
 
			status = pbnetwork::STATUS_AWAY;
 
			break;
 
		}
 
		case PURPLE_STATUS_UNAVAILABLE: {
 
			status = pbnetwork::STATUS_DND;
 
			break;
 
		}
 
		case PURPLE_STATUS_EXTENDED_AWAY: {
 
			status = pbnetwork::STATUS_XA;
 
			break;
 
		}
 
		case PURPLE_STATUS_OFFLINE: {
 
			status = pbnetwork::STATUS_NONE;
 
			break;
 
		}
 
		default:
 
			status = pbnetwork::STATUS_ONLINE;
 
			break;
 
	}
 

	
 
	const char *message = purple_status_get_attr_string_wrapped(stat, "message");
 

	
 
	if (message != NULL) {
 
		char *stripped = purple_markup_strip_html_wrapped(message);
 
		statusMessage = std::string(stripped);
 
		g_free(stripped);
 
	}
 
	else
 
		statusMessage = "";
 
	return true;
 
}
 

	
 
static std::string getIconHash(PurpleBuddy *m_buddy) {
 
	char *avatarHash = NULL;
 
	PurpleBuddyIcon *icon = purple_buddy_icons_find_wrapped(purple_buddy_get_account_wrapped(m_buddy), purple_buddy_get_name_wrapped(m_buddy));
 
	if (icon) {
 
		avatarHash = purple_buddy_icon_get_full_path_wrapped(icon);
 
		purple_buddy_icon_unref_wrapped(icon);
 
	}
 

	
 
	if (avatarHash) {
 
		// Check if it's patched libpurple which saves icons to directories
 
		char *hash = strrchr(avatarHash,'/');
 
		std::string h;
 
		if (hash) {
 
			char *dot;
 
			hash++;
 
			dot = strchr(hash, '.');
 
			if (dot)
 
				*dot = '\0';
 

	
 
			std::string ret(hash);
 
			g_free(avatarHash);
 
			return ret;
 
		}
 
		else {
 
			std::string ret(avatarHash);
 
			g_free(avatarHash);
 
			return ret;
 
		}
 
	}
 

	
 
	return "";
 
}
 

	
 
static std::vector<std::string> getGroups(PurpleBuddy *m_buddy) {
 
	std::vector<std::string> groups;
 
	if (purple_buddy_get_name_wrapped(m_buddy)) {
 
		GSList *buddies = purple_find_buddies_wrapped(purple_buddy_get_account_wrapped(m_buddy), purple_buddy_get_name_wrapped(m_buddy));
 
		while(buddies) {
 
			PurpleGroup *g = purple_buddy_get_group_wrapped((PurpleBuddy *) buddies->data);
 
			buddies = g_slist_delete_link(buddies, buddies);
 

	
 
			if(g && purple_group_get_name_wrapped(g)) {
 
				groups.push_back(purple_group_get_name_wrapped(g));
 
			}
 
		}
 
	}
 

	
 
	if (groups.empty()) {
 
		groups.push_back("Buddies");
 
	}
 

	
 
	return groups;
 
}
 

	
 
void buddyListNewNode(PurpleBlistNode *node);
 

	
 
static gboolean new_node_cache(void *data) {
 
	NodeCache *cache = (NodeCache *) data;
 
	caching = false;
 
	for (std::map<PurpleBlistNode *, int>::const_iterator it = cache->nodes.begin(); it != cache->nodes.end(); it++) {
 
		buddyListNewNode(it->first);
 
	}
 
	caching = true;
 

	
 
	cache->account->ui_data = NULL;
 
	delete cache;
 

	
 
	return FALSE;
 
}
 

	
 
static void buddyNodeRemoved(PurpleBuddyList *list, PurpleBlistNode *node) {
 
	if (!PURPLE_BLIST_NODE_IS_BUDDY_WRAPPED(node))
 
		return;
 
	PurpleBuddy *buddy = (PurpleBuddy *) node;
 
	PurpleAccount *account = purple_buddy_get_account_wrapped(buddy);
 

	
 
	if (!account->ui_data) {
 
		return;
 
	}
 

	
 
	NodeCache *cache = (NodeCache *) account->ui_data;
 
	cache->nodes.erase(node);
 
}
 

	
 
void buddyListNewNode(PurpleBlistNode *node) {
 
	if (!PURPLE_BLIST_NODE_IS_BUDDY_WRAPPED(node))
 
		return;
 
	PurpleBuddy *buddy = (PurpleBuddy *) node;
 
	PurpleAccount *account = purple_buddy_get_account_wrapped(buddy);
 

	
 
	if (caching) {
 
		if (!account->ui_data) {
 
			NodeCache *cache = new NodeCache;
 
			cache->account = account;
 
			cache->timer = purple_timeout_add_wrapped(400, new_node_cache, cache);
 
			account->ui_data = (void *) cache;
 
		}
 

	
 
		NodeCache *cache = (NodeCache *) account->ui_data;
 
		cache->nodes[node] = 1;
 
		return;
 
	}
 
	
 

	
 
	std::vector<std::string> groups = getGroups(buddy);
 
	LOG4CXX_INFO(logger, "Buddy updated " << np->m_accounts[account] << " " << purple_buddy_get_name_wrapped(buddy) << " " << getAlias(buddy) << " group (" << groups.size() << ")=" << groups[0]);
 

	
 
	// Status
 
	pbnetwork::StatusType status = pbnetwork::STATUS_NONE;
 
	std::string message;
 
	getStatus(buddy, status, message);
 

	
 
	// Tooltip
 
	PurplePlugin *prpl = purple_find_prpl_wrapped(purple_account_get_protocol_id_wrapped(account));
 
	PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
 

	
 
	bool blocked = false;
 
	if (CONFIG_BOOL(config, "service.enable_privacy_lists")) {
 
		if (prpl_info && prpl_info->tooltip_text) {
 
			PurpleNotifyUserInfo *user_info = purple_notify_user_info_new_wrapped();
 
			prpl_info->tooltip_text(buddy, user_info, true);
 
			GList *entries = purple_notify_user_info_get_entries_wrapped(user_info);
 

	
 
			while (entries) {
 
				PurpleNotifyUserInfoEntry *entry = (PurpleNotifyUserInfoEntry *)(entries->data);
 
				if (purple_notify_user_info_entry_get_label_wrapped(entry) && purple_notify_user_info_entry_get_value_wrapped(entry)) {
 
					std::string label = purple_notify_user_info_entry_get_label_wrapped(entry);
 
					if (label == "Blocked" ) {
 
						if (std::string(purple_notify_user_info_entry_get_value_wrapped(entry)) == "Yes") {
 
							blocked = true;
 
							break;
 
						}
 
					}
 
				}
 
				entries = entries->next;
 
			}
 
			purple_notify_user_info_destroy_wrapped(user_info);
 
		}
 

	
 
		if (!blocked) {
 
			blocked = purple_privacy_check_wrapped(account, purple_buddy_get_name_wrapped(buddy)) == false;
 
		}
 
		else {
 
			bool purpleBlocked = purple_privacy_check_wrapped(account, purple_buddy_get_name_wrapped(buddy)) == false;
 
			if (blocked != purpleBlocked) {
 
				purple_privacy_deny_wrapped(account, purple_buddy_get_name_wrapped(buddy), FALSE, FALSE);
 
			}
 
		}
 
	}
 

	
 
	np->handleBuddyChanged(np->m_accounts[account], purple_buddy_get_name_wrapped(buddy), getAlias(buddy), getGroups(buddy), status, message, getIconHash(buddy),
 
		blocked
 
	);
 
}
 

	
 
static void buddyListUpdate(PurpleBuddyList *list, PurpleBlistNode *node) {
 
	if (!PURPLE_BLIST_NODE_IS_BUDDY_WRAPPED(node))
 
		return;
 
	buddyListNewNode(node);
 
}
 

	
 
static void buddyPrivacyChanged(PurpleBlistNode *node, void *data) {
 
	if (!PURPLE_BLIST_NODE_IS_BUDDY_WRAPPED(node))
 
		return;
 
	buddyListUpdate(NULL, node);
 
}
 

	
 
static void NodeRemoved(PurpleBlistNode *node, void *data) {
 
	if (!PURPLE_BLIST_NODE_IS_BUDDY_WRAPPED(node))
 
		return;
 
// 	PurpleBuddy *buddy = (PurpleBuddy *) node;
 
}
 

	
 
static void buddyListSaveNode(PurpleBlistNode *node) {
 
	if (!PURPLE_BLIST_NODE_IS_BUDDY_WRAPPED(node))
 
		return;
 
}
 

	
 
static void buddyListSaveAccount(PurpleAccount *account) {
 
}
 

	
 
static void buddyListRemoveNode(PurpleBlistNode *node) {
 
	if (!PURPLE_BLIST_NODE_IS_BUDDY_WRAPPED(node))
 
		return;
 
}
 

	
 
static PurpleBlistUiOps blistUiOps =
 
{
 
	NULL,
 
	buddyListNewNode,
 
	NULL,
 
	buddyListUpdate,
 
	buddyNodeRemoved,
 
	NULL,
 
	NULL,
 
	NULL, // buddyListAddBuddy,
 
	NULL,
 
	NULL,
 
	buddyListSaveNode,
 
	buddyListRemoveNode,
 
	buddyListSaveAccount,
 
	NULL
 
};
 

	
 
static void conv_write(PurpleConversation *conv, const char *who, const char *alias, const char *msg, PurpleMessageFlags flags, time_t mtime) {
 
	if (flags & PURPLE_MESSAGE_SYSTEM && CONFIG_STRING(config, "service.protocol") == "prpl-telegram") {
 
		PurpleAccount *account = purple_conversation_get_account_wrapped(conv);
 

	
 
	// 	char *striped = purple_markup_strip_html_wrapped(message);
 
	// 	std::string msg = striped;
 
	// 	g_free(striped);
 

	
 

	
 
		// Escape HTML characters.
 
		char *newline = purple_strdup_withhtml_wrapped(msg);
 
		char *strip, *xhtml;
 
		purple_markup_html_to_xhtml_wrapped(newline, &xhtml, &strip);
 
	// 	xhtml_linkified = spectrum_markup_linkify(xhtml);
 
		std::string message_(strip);
 

	
 
		std::string xhtml_(xhtml);
 
		g_free(newline);
 
		g_free(xhtml);
 
	// 	g_free(xhtml_linkified);
 
		g_free(strip);
 

	
 
		// AIM and XMPP adds <body>...</body> here...
 
		if (xhtml_.find("<body>") == 0) {
 
			xhtml_ = xhtml_.substr(6);
 
			if (xhtml_.find("</body>") != std::string::npos) {
 
				xhtml_ = xhtml_.substr(0, xhtml_.find("</body>"));
 
			}
 
		}
 

	
 
		if (xhtml_ == message_) {
 
			xhtml_ = "";
 
		}
 

	
 
		std::string timestamp;
 
		if (mtime && (unsigned long) time(NULL)-10 > (unsigned long) mtime/* && (unsigned long) time(NULL) - 31536000 < (unsigned long) mtime*/) {
 
			char buf[80];
 
			strftime(buf, sizeof(buf), "%Y%m%dT%H%M%S", gmtime(&mtime));
 
			timestamp = buf;
 
		}
 

	
 
	// 	LOG4CXX_INFO(logger, "Received message body='" << message_ << "' xhtml='" << xhtml_ << "'");
 

	
 
		if (purple_conversation_get_type_wrapped(conv) == PURPLE_CONV_TYPE_IM) {
 
			std::string w = purple_normalize_wrapped(account, who);
 
			size_t pos = w.find("/");
 
			if (pos != std::string::npos)
 
				w.erase((int) pos, w.length() - (int) pos);
 
			np->handleMessage(np->m_accounts[account], w, message_, "", xhtml_, timestamp);
 
		}
 
		else {
 
			LOG4CXX_INFO(logger, "Received message body='" << message_ << "' name='" << purple_conversation_get_name_wrapped(conv) << "' " << who);
 
			np->handleMessage(np->m_accounts[account], purple_conversation_get_name_wrapped(conv), message_, who, xhtml_, timestamp);
 
		}
 
	}
 
}
 

	
 
static char *calculate_data_hash(guchar *data, size_t len,
 
    const gchar *hash_algo)
 
{
 
	PurpleCipherContext *context;
 
	static gchar digest[129]; /* 512 bits hex + \0 */
 

	
 
	context = purple_cipher_context_new_by_name(hash_algo, NULL);
 
	if (context == NULL)
 
	{
 
		purple_debug_error("jabber", "Could not find %s cipher\n", hash_algo);
 
		g_return_val_if_reached(NULL);
 
	}
 

	
 
	/* Hash the data */
 
	purple_cipher_context_append(context, data, len);
 
	if (!purple_cipher_context_digest_to_str(context, sizeof(digest), digest, NULL))
 
	{
 
		purple_debug_error("jabber", "Failed to get digest for %s cipher.\n",
 
		    hash_algo);
 
		g_return_val_if_reached(NULL);
 
	}
 
	purple_cipher_context_destroy(context);
 

	
 
	return g_strdup(digest);
 
}
 

	
 
static void conv_write_im(PurpleConversation *conv, const char *who, const char *msg, PurpleMessageFlags flags, time_t mtime) {
 
	// Don't forwards our own messages.
 
	if (purple_conversation_get_type_wrapped(conv) == PURPLE_CONV_TYPE_IM && (flags & PURPLE_MESSAGE_SEND || flags & PURPLE_MESSAGE_SYSTEM)) {
 
		return;
 
	}
 
	PurpleAccount *account = purple_conversation_get_account_wrapped(conv);
 

	
 
	std::string message_;
 
	std::string xhtml_;
 

	
 
	if (flags & PURPLE_MESSAGE_IMAGES && !CONFIG_STRING(config, "service.web_directory").empty() && !CONFIG_STRING(config, "service.web_url").empty() ) {
 
		LOG4CXX_INFO(logger, "Received image body='" << msg << "'");
 
		std::string body = msg;
 
		std::string plain = msg;
 
		size_t i;
 
		while ((i = body.find("<img id=\"")) != std::string::npos) {
 
			int from = i + strlen("<img id=\"");
 
			int to = body.find("\"", from + 1);
 
			std::string id = body.substr(from, to - from);
 
			LOG4CXX_INFO(logger, "Image ID = '" << id << "' " << from << " " << to);
 

	
 
			PurpleStoredImage *image = purple_imgstore_find_by_id(atoi(id.c_str()));
 
			if (!image) {
 
				LOG4CXX_ERROR(logger, "Cannot find image with id " << id << ".");
 
				return;
 
			}
 

	
 
			std::string ext = "icon";
 
			std::string name;
 
			guchar * data = (guchar *) purple_imgstore_get_data_wrapped(image);
 
			size_t len = purple_imgstore_get_size_wrapped(image);
 
			if (len < 300000 && data) {
 
				ext = purple_imgstore_get_extension(image);
 
				char *hash = calculate_data_hash(data, len, "sha1");
 
				if (!hash) {
 
					return;
 
				}
 
				name = hash;
 
				g_free(hash);
 

	
 
				std::ofstream output;
 
				output.open(std::string(CONFIG_STRING(config, "service.web_directory") + "/" + name + "." + ext).c_str(), std::ios::out | std::ios::binary);
 
				output.write((char *)data, len);
 
				output.close();
 
			}
 
			else {
 
				purple_imgstore_unref_wrapped(image);
 
				return;
 
			}
 
			purple_imgstore_unref_wrapped(image);
 
			
 
			std::string src = CONFIG_STRING(config, "service.web_url") + "/" + name + "." + ext;
 
			std::string img = "<img src=\"" + src + "\"/>";
 
			boost::replace_all(body, "<img id=\"" + id + "\">", img);
 
			boost::replace_all(plain, "<img id=\"" + id + "\">", src);
 
		}
 
		LOG4CXX_INFO(logger, "New image body='" << body << "'");
 
		char *strip, *xhtml;
 
		purple_markup_html_to_xhtml_wrapped(body.c_str(), &xhtml, &strip);
 
		message_ = strip;
 
		if (message_.empty()) {
 
			message_ = plain;
 
		}
 
		xhtml_ = xhtml;
 
		g_free(xhtml);
 
		g_free(strip);
 
	}
 
	else {
 
		// Escape HTML characters.
 
		char *newline = purple_strdup_withhtml_wrapped(msg);
 
		char *strip, *xhtml;
 
		purple_markup_html_to_xhtml_wrapped(newline, &xhtml, &strip);
 
		message_ = strip;
 
		xhtml_ = xhtml;
 
		g_free(newline);
 
		g_free(xhtml);
 
		g_free(strip);
 
	}
 

	
 
	// AIM and XMPP adds <body>...</body> here...
 
	if (xhtml_.find("<body>") == 0) {
 
		xhtml_ = xhtml_.substr(6);
 
		if (xhtml_.find("</body>") != std::string::npos) {
 
			xhtml_ = xhtml_.substr(0, xhtml_.find("</body>"));
 
		}
 
	}
 

	
 
	if (xhtml_ == message_) {
 
		xhtml_ = "";
 
	}
 

	
 
	std::string timestamp;
 
	if (mtime && (unsigned long) time(NULL)-10 > (unsigned long) mtime/* && (unsigned long) time(NULL) - 31536000 < (unsigned long) mtime*/) {
 
		char buf[80];
 
		strftime(buf, sizeof(buf), "%Y%m%dT%H%M%S", gmtime(&mtime));
 
		timestamp = buf;
 
	}
 

	
 
	if (purple_conversation_get_type_wrapped(conv) == PURPLE_CONV_TYPE_IM) {
 
		std::string w = purple_normalize_wrapped(account, who);
 
		std::string n;
 
		size_t pos = w.find("/");
 
		if (pos != std::string::npos) {
 
			n = w.substr((int) pos + 1, w.length() - (int) pos);
 
			w.erase((int) pos, w.length() - (int) pos);
 
		}
 
		LOG4CXX_INFO(logger, "Received message body='" << message_ << "' xhtml='" << xhtml_ << "' name='" << w << "'");
 
		np->handleMessage(np->m_accounts[account], w, message_, n, xhtml_, timestamp);
 
	}
 
	else {
 
		LOG4CXX_INFO(logger, "Received message body='" << message_ << "' xhtml='" << xhtml_ << "' name='" << purple_conversation_get_name_wrapped(conv) << "' " << who);
 
		np->handleMessage(np->m_accounts[account], purple_conversation_get_name_wrapped(conv), message_, who, xhtml_, timestamp);
 
	}
 
}
 

	
 
static void conv_chat_add_users(PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals) {
 
	PurpleAccount *account = purple_conversation_get_account_wrapped(conv);
 

	
 
	GList *l = cbuddies;
 
	while (l != NULL) {
 
		PurpleConvChatBuddy *cb = (PurpleConvChatBuddy *)l->data;
 
		std::string name(cb->name);
 
		std::string alias = cb->alias ? cb->alias : cb->name;
 
		int flags = GPOINTER_TO_INT(cb->flags);
 
		if (flags & PURPLE_CBFLAGS_OP || flags & PURPLE_CBFLAGS_HALFOP) {
 
// 			item->addAttribute("affiliation", "admin");
 
// 			item->addAttribute("role", "moderator");
 
			flags = 1;
 
		}
 
		else if (flags & PURPLE_CBFLAGS_FOUNDER) {
 
// 			item->addAttribute("affiliation", "owner");
 
// 			item->addAttribute("role", "moderator");
 
			flags = 1;
 
		}
 
		else {
 
			flags = 0;
 
// 			item->addAttribute("affiliation", "member");
 
// 			item->addAttribute("role", "participant");
 
		}
 

	
 
		np->handleParticipantChanged(np->m_accounts[account], name, purple_conversation_get_name_wrapped(conv), (int) flags, pbnetwork::STATUS_ONLINE, "", "", alias);
 

	
 
		l = l->next;
 
	}
 
}
 

	
 
static void conv_chat_remove_users(PurpleConversation *conv, GList *users) {
 
	PurpleAccount *account = purple_conversation_get_account_wrapped(conv);
 

	
 
	GList *l = users;
 
	while (l != NULL) {
 
		std::string name((char *) l->data);
 
		np->handleParticipantChanged(np->m_accounts[account], name, purple_conversation_get_name_wrapped(conv), 0, pbnetwork::STATUS_NONE);
 

	
 
		l = l->next;
 
	}
 
}
 

	
 
static gboolean conv_has_focus(PurpleConversation *conv) {
 
	return TRUE;
 
}
 

	
 
static void conv_chat_topic_changed(PurpleConversation *conv, const char *who, const char *topic) {
 
	LOG4CXX_INFO(logger, "Conversation topic changed");
 
	PurpleAccount *account = purple_conversation_get_account_wrapped(conv);
 
	np->handleSubject(np->m_accounts[account], purple_conversation_get_name_wrapped(conv), topic ? topic : "", who ? who : "Spectrum 2");
 
}
 

	
 
static void conv_present(PurpleConversation *conv) {
 
	if (purple_conversation_get_type_wrapped(conv) == PURPLE_CONV_TYPE_CHAT) {
 
		LOG4CXX_INFO(logger, "Conversation presented");
 
		conv_chat_add_users(conv, PURPLE_CONV_CHAT_WRAPPED(conv)->in_room, TRUE);
 
		const char *topic = purple_conv_chat_get_topic(PURPLE_CONV_CHAT_WRAPPED(conv));
 
		if (topic && *topic != '\0') {
 
			conv_chat_topic_changed(conv, topic, PURPLE_CONV_CHAT_WRAPPED(conv)->who);
 
		}
 
		else {
 
			LOG4CXX_INFO(logger, "Conversation created with an empty topic");
 
		}
 
	}
 
}
 

	
 
static PurpleConversationUiOps conversation_ui_ops =
 
{
 
	NULL,
 
	NULL,
 
	conv_write_im,//conv_write_chat,                              /* write_chat           */
 
	conv_write_im,             /* write_im             */
 
	conv_write,//conv_write_conv,           /* write_conv           */
 
	conv_chat_add_users,       /* chat_add_users       */
 
	NULL,//conv_chat_rename_user,     /* chat_rename_user     */
 
	conv_chat_remove_users,    /* chat_remove_users    */
 
	NULL,//pidgin_conv_chat_update_user,     /* chat_update_user     */
 
	conv_present,//pidgin_conv_present_conversation, /* present              */
 
	conv_has_focus,//pidgin_conv_has_focus,            /* has_focus            */
 
	NULL,//pidgin_conv_custom_smiley_add,    /* custom_smiley_add    */
 
	NULL,//pidgin_conv_custom_smiley_write,  /* custom_smiley_write  */
 
	NULL,//pidgin_conv_custom_smiley_close,  /* custom_smiley_close  */
 
	NULL,//pidgin_conv_send_confirm,         /* send_confirm         */
 
	NULL,
 
	NULL,
 
	NULL,
 
	NULL
 
};
 

	
 
struct Dis {
 
	std::string name;
 
	std::string protocol;
 
};
 

	
 
static gboolean disconnectMe(void *data) {
 
	Dis *d = (Dis *) data;
 
	PurpleAccount *account = purple_accounts_find_wrapped(d->name.c_str(), d->protocol.c_str());
 
	delete d;
 

	
 
	if (account) {
 
		np->handleLogoutRequest(np->m_accounts[account], purple_account_get_username_wrapped(account));
 
	}
 
	return FALSE;
 
}
 

	
 
static gboolean pingTimeout(void *data) {
 
	np->checkPing();
 
	return TRUE;
 
}
 

	
 
static void connection_report_disconnect(PurpleConnection *gc, PurpleConnectionError reason, const char *text){
 
	PurpleAccount *account = purple_connection_get_account_wrapped(gc);
 
	np->handleDisconnected(np->m_accounts[account], (int) reason, text ? text : "");
 
// 	Dis *d = new Dis;
 
// 	d->name = purple_account_get_username_wrapped(account);
 
// 	d->protocol = purple_account_get_protocol_id_wrapped(account);
 
// 	purple_timeout_add_seconds_wrapped(10, disconnectMe, d);
 
}
 

	
 
static PurpleConnectionUiOps conn_ui_ops =
 
{
 
	NULL,
 
	NULL,
 
	NULL,//connection_disconnected,
 
	NULL,
 
	NULL,
 
	NULL,
 
	NULL,
 
	connection_report_disconnect,
 
	NULL,
 
	NULL,
 
	NULL
 
};
 

	
 
static void *notify_user_info(PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info) {
 
	PurpleAccount *account = purple_connection_get_account_wrapped(gc);
 
	std::string name(purple_normalize_wrapped(account, who));
 

	
 
	size_t pos = name.find("/");
 
	if (pos != std::string::npos)
 
		name.erase((int) pos, name.length() - (int) pos);
 

	
 
	
 
	GList *vcardEntries = purple_notify_user_info_get_entries_wrapped(user_info);
 
	PurpleNotifyUserInfoEntry *vcardEntry;
 
	std::string firstName;
 
	std::string lastName;
 
	std::string fullName;
 
	std::string nickname;
 
	std::string header;
 
	std::string label;
 
	std::string photo;
 

	
 
	while (vcardEntries) {
 
		vcardEntry = (PurpleNotifyUserInfoEntry *)(vcardEntries->data);
 
		if (purple_notify_user_info_entry_get_label_wrapped(vcardEntry) && purple_notify_user_info_entry_get_value_wrapped(vcardEntry)){
 
			label = purple_notify_user_info_entry_get_label_wrapped(vcardEntry);
 
			if (label == "Given Name" || label == "First Name") {
 
				firstName = purple_notify_user_info_entry_get_value_wrapped(vcardEntry);
 
			}
 
			else if (label == "Family Name" || label == "Last Name") {
 
				lastName = purple_notify_user_info_entry_get_value_wrapped(vcardEntry);
 
			}
 
			else if (label=="Nickname" || label == "Nick") {
 
				nickname = purple_notify_user_info_entry_get_value_wrapped(vcardEntry);
 
			}
 
			else if (label=="Full Name" || label == "Display name") {
 
				fullName = purple_notify_user_info_entry_get_value_wrapped(vcardEntry);
 
			}
 
			else {
 
				LOG4CXX_WARN(logger, "Unhandled VCard Label '" << purple_notify_user_info_entry_get_label_wrapped(vcardEntry) << "' " << purple_notify_user_info_entry_get_value_wrapped(vcardEntry));
 
			}
 
		}
 
		vcardEntries = vcardEntries->next;
 
	}
 

	
 
	if ((!firstName.empty() || !lastName.empty()) && fullName.empty())
 
		fullName = firstName + " " + lastName;
 

	
 
	if (nickname.empty() && !fullName.empty()) {
 
		nickname = fullName;
 
	}
 

	
 
	bool ownInfo = name == purple_account_get_username_wrapped(account);
 

	
 
	if (ownInfo) {
 
		const gchar *displayname = purple_connection_get_display_name_wrapped(gc);
 
#if PURPLE_MAJOR_VERSION >= 2 && PURPLE_MINOR_VERSION >= 7
 
		if (!displayname) {
 
			displayname = purple_account_get_name_for_display_wrapped(account);
 
		}
 
#endif
 

	
 
		if (displayname && nickname.empty()) {
 
			nickname = displayname;
 
		}
 

	
 
		// avatar
 
		PurpleStoredImage *avatar = purple_buddy_icons_find_account_icon_wrapped(account);
 
		if (avatar) {
 
			const gchar * data = (const gchar *) purple_imgstore_get_data_wrapped(avatar);
 
			size_t len = purple_imgstore_get_size_wrapped(avatar);
 
			if (len < 300000 && data) {
 
				photo = std::string(data, len);
 
			}
 
			purple_imgstore_unref_wrapped(avatar);
 
		}
 
	}
 

	
 
	PurpleBuddy *buddy = purple_find_buddy_wrapped(purple_connection_get_account_wrapped(gc), who);
 
	if (buddy && photo.size() == 0) {
 
		gsize len;
 
		PurpleBuddyIcon *icon = NULL;
 
		icon = purple_buddy_icons_find_wrapped(purple_connection_get_account_wrapped(gc), name.c_str());
 
		if (icon) {
 
			if (true) {
 
				gchar *data;
 
				gchar *path = purple_buddy_icon_get_full_path_wrapped(icon);
 
				if (path) {
 
					if (g_file_get_contents(path, &data, &len, NULL)) {
 
						photo = std::string(data, len);
 
						g_free(data);
 
					}
 
					g_free(path);
 
				}
 
			}
 
			else {
 
				const gchar * data = (gchar*)purple_buddy_icon_get_data_wrapped(icon, &len);
 
				if (len < 300000 && data) {
 
					photo = std::string(data, len);
 
				}
 
			}
 
			purple_buddy_icon_unref_wrapped(icon);
 
		}
 
	}
 

	
 
	np->handleVCard(np->m_accounts[account], np->m_vcards[np->m_accounts[account] + name], name, fullName, nickname, photo);
 
	np->m_vcards.erase(np->m_accounts[account] + name);
 

	
 
	return NULL;
 
}
 

	
 
static PurpleNotifyUiOps notifyUiOps =
 
{
 
		NULL,
 
		NULL,
 
		NULL,
 
		NULL,
 
		NULL,
 
		NULL,
 
		notify_user_info,
 
		NULL,
 
		NULL,
 
		NULL,
 
		NULL,
 
		NULL,
 
		NULL
 
};
 

	
 
static PurpleRequestUiOps requestUiOps =
 
{
 
	requestInput,
 
	NULL,
 
	requestAction,
 
	NULL,
 
	NULL,
 
	NULL, //requestClose,
 
	NULL,
 
	NULL,
 
	NULL,
 
	NULL,
 
	NULL
 
};
 

	
 
static void * accountRequestAuth(PurpleAccount *account, const char *remote_user, const char *id, const char *alias, const char *message, gboolean on_list, PurpleAccountRequestAuthorizationCb authorize_cb, PurpleAccountRequestAuthorizationCb deny_cb, void *user_data) {
 
	authRequest *req = new authRequest;
 
	req->authorize_cb = authorize_cb;
 
	req->deny_cb = deny_cb;
 
	req->user_data = user_data;
 
	req->account = account;
 
	req->who = remote_user;
 
	req->mainJID = np->m_accounts[account];
 
	np->m_authRequests[req->mainJID + req->who] = req;
 

	
 
	np->handleAuthorization(req->mainJID, req->who);
 

	
 
	return req;
 
}
 

	
 
static void accountRequestClose(void *data){
 
	authRequest *req = (authRequest *) data;
 
	np->m_authRequests.erase(req->mainJID + req->who);
 
}
 

	
 

	
 
static PurpleAccountUiOps accountUiOps =
 
{
 
	NULL,
 
	NULL,
 
	NULL,
 
	accountRequestAuth,
 
	accountRequestClose,
 
	NULL,
 
	NULL,
 
	NULL,
 
	NULL
 
};
 

	
 
static void XferCreated(PurpleXfer *xfer) {
 
	if (!xfer) {
 
		return;
 
	}
 

	
 
// 	PurpleAccount *account = purple_xfer_get_account_wrapped(xfer);
 
// 	np->handleFTStart(np->m_accounts[account], xfer->who, xfer, "", xhtml_);
 
}
 

	
 
static void XferDestroyed(PurpleXfer *xfer) {
 
	std::remove(np->m_waitingXfers.begin(), np->m_waitingXfers.end(), xfer);
 
	FTData *ftdata = (FTData *) xfer->ui_data;
 
	if (ftdata && ftdata->timer) {
 
		purple_timeout_remove_wrapped(ftdata->timer);
 
	}
 
	if (ftdata) {
 
		np->m_xfers.erase(ftdata->id);
 
	}
 
}
 

	
 
static void xferCanceled(PurpleXfer *xfer) {
 
	PurpleAccount *account = purple_xfer_get_account_wrapped(xfer);
 
	std::string filename(xfer ? purple_xfer_get_filename_wrapped(xfer) : "");
 
	std::string w = xfer->who;
 
	size_t pos = w.find("/");
 
	if (pos != std::string::npos)
 
		w.erase((int) pos, w.length() - (int) pos);
 

	
 
	FTData *ftdata = (FTData *) xfer->ui_data;
 

	
 
	np->handleFTFinish(np->m_accounts[account], w, filename, purple_xfer_get_size_wrapped(xfer), ftdata ? ftdata->id : 0);
 
	std::remove(np->m_waitingXfers.begin(), np->m_waitingXfers.end(), xfer);
 
	if (ftdata && ftdata->timer) {
 
		purple_timeout_remove_wrapped(ftdata->timer);
 
	}
 
	purple_xfer_unref_wrapped(xfer);
 
}
 

	
 
static void fileSendStart(PurpleXfer *xfer) {
 
// 	FiletransferRepeater *repeater = (FiletransferRepeater *) xfer->ui_data;
 
// 	repeater->fileSendStart();
 
}
 

	
 
static void fileRecvStart(PurpleXfer *xfer) {
 
// 	FiletransferRepeater *repeater = (FiletransferRepeater *) xfer->ui_data;
 
// 	repeater->fileRecvStart();
 
	FTData *ftData = (FTData *) xfer->ui_data;
 
	if (ftData->timer == 0) {
 
		ftData->timer = purple_timeout_add_wrapped(1, ft_ui_ready, xfer);
 
	}
 
}
 

	
 
static void newXfer(PurpleXfer *xfer) {
 
	PurpleAccount *account = purple_xfer_get_account_wrapped(xfer);
 
	std::string filename(xfer ? purple_xfer_get_filename_wrapped(xfer) : "");
 
	purple_xfer_ref_wrapped(xfer);
 
	std::string w = xfer->who;
 
	size_t pos = w.find("/");
 
	if (pos != std::string::npos)
 
		w.erase((int) pos, w.length() - (int) pos);
 

	
 
	FTData *ftdata = new FTData;
 
	ftdata->paused = false;
 
	ftdata->id = 0;
 
	ftdata->timer = 0;
 
	xfer->ui_data = (void *) ftdata;
 

	
 
	np->m_unhandledXfers[np->m_accounts[account] + filename + w] = xfer;
 

	
 
	np->handleFTStart(np->m_accounts[account], w, filename, purple_xfer_get_size_wrapped(xfer));
 
}
 

	
 
static void XferReceiveComplete(PurpleXfer *xfer) {
 
// 	FiletransferRepeater *repeater = (FiletransferRepeater *) xfer->ui_data;
 
// 	repeater->_tryToDeleteMe();
 
// 	GlooxMessageHandler::instance()->ftManager->handleXferFileReceiveComplete(xfer);
 
	std::remove(np->m_waitingXfers.begin(), np->m_waitingXfers.end(), xfer);
 
	FTData *ftdata = (FTData *) xfer->ui_data;
 
	if (ftdata && ftdata->timer) {
 
		purple_timeout_remove_wrapped(ftdata->timer);
 
	}
 
	purple_xfer_unref_wrapped(xfer);
 
}
 

	
 
static void XferSendComplete(PurpleXfer *xfer) {
 
// 	FiletransferRepeater *repeater = (FiletransferRepeater *) xfer->ui_data;
 
// 	repeater->_tryToDeleteMe();
 
	std::remove(np->m_waitingXfers.begin(), np->m_waitingXfers.end(), xfer);
 
	FTData *ftdata = (FTData *) xfer->ui_data;
 
	if (ftdata && ftdata->timer) {
 
		purple_timeout_remove_wrapped(ftdata->timer);
 
	}
 
	purple_xfer_unref_wrapped(xfer);
 
}
 

	
 
static gssize XferWrite(PurpleXfer *xfer, const guchar *buffer, gssize size) {
 
	FTData *ftData = (FTData *) xfer->ui_data;
 
	std::string data((const char *) buffer, (size_t) size);
 
// 	std::cout << "xferwrite\n";
 
	if (!ftData->paused) {
 
// 		std::cout << "adding xfer to waitingXfers queue\n";
 
		np->m_waitingXfers.push_back(xfer);
 
	}
 
	np->handleFTData(ftData->id, data);
 
	return size;
 
}
 

	
 
static void XferNotSent(PurpleXfer *xfer, const guchar *buffer, gsize size) {
 
// 	FiletransferRepeater *repeater = (FiletransferRepeater *) xfer->ui_data;
 
// 	repeater->handleDataNotSent(buffer, size);
 
}
 

	
 
static gssize XferRead(PurpleXfer *xfer, guchar **buffer, gssize size) {
 
// 	FiletransferRepeater *repeater = (FiletransferRepeater *) xfer->ui_data;
 
// 	int data_size = repeater->getDataToSend(buffer, size);
 
// 	if (data_size == 0)
 
// 		return 0;
 
// 	
 
// 	return data_size;
 
	return 0;
 
}
 

	
 
static PurpleXferUiOps xferUiOps =
 
{
 
	XferCreated,
 
	XferDestroyed,
 
	NULL,
 
	NULL,
 
	xferCanceled,
 
	xferCanceled,
 
	XferWrite,
 
	XferRead,
 
	XferNotSent,
 
	NULL
 
};
 

	
 
static void RoomlistProgress(PurpleRoomlist *list, gboolean in_progress)
 
{
 
	if (!in_progress) {
 
		GList *fields = purple_roomlist_get_fields(list);
 
		GList *field;
 
		int topicId = -1;
 
		int usersId = -1;
 
		int id = 0;
 
		for (field = fields; field != NULL; field = field->next, id++) {
 
			PurpleRoomlistField *f = (PurpleRoomlistField *) field->data;
 
			if (!f || !f->name) {
 
				continue;
 
			}
 
			std::string fstring = f->name;
 
			if (fstring == "topic") {
 
				topicId = id;
 
			}
 
			else if (fstring == "users") {
 
				usersId = id;
 
			}
 
			else {
 
				LOG4CXX_INFO(logger, "Uknown RoomList field " << fstring);
 
			}
 
		}
 

	
 
		GList *rooms;
 
		std::list<std::string> m_rooms;
 
		std::list<std::string> m_topics;
 
		for (rooms = list->rooms; rooms != NULL; rooms = rooms->next) {
 
			PurpleRoomlistRoom *room = (PurpleRoomlistRoom *)rooms->data;	
 
			m_rooms.push_back(room->name);
 

	
 
			if (topicId == -1) {
 
				m_topics.push_back(room->name);
 
			}
 
			else {
 
				char *topic = (char *) g_list_nth_data(purple_roomlist_room_get_fields(room), topicId);
 
				if (topic) {
 
					m_topics.push_back(topic);
 
				}
 
				else {
 
					if (usersId) {
 
						char *users = (char *) g_list_nth_data(purple_roomlist_room_get_fields(room), usersId);
 
						if (users) {
 
							m_topics.push_back(users);
 
						}
 
						else {
 
							LOG4CXX_WARN(logger, "RoomList topic and users is NULL");
 
							m_topics.push_back(room->name);
 
						}
 
					}
 
					else {
 
						LOG4CXX_WARN(logger, "RoomList topic is NULL");
 
						m_topics.push_back(room->name);
 
					}
 
				}
 
			}
 
		}
 

	
 
		std::string user = "";
 
		if (list->account) {
 
			user = np->m_accounts[list->account];
 
		}
 

	
 
		LOG4CXX_INFO(logger, "RoomList is fetched for user " << user);
 
		np->handleRoomList(user, m_rooms, m_topics);
 
	}
 
	else {
 
		LOG4CXX_INFO(logger, "RoomList is still in progress");
 
	}
 
}
 

	
 
static PurpleRoomlistUiOps roomlist_ui_ops =
 
{
 
	NULL,
 
	NULL,
 
	NULL,
 
	NULL,
 
	RoomlistProgress,
 
	NULL,
 
	NULL,
 
	NULL,
 
	NULL,
 
	NULL
 
};
 

	
 
static void transport_core_ui_init(void)
 
{
 
	purple_blist_set_ui_ops_wrapped(&blistUiOps);
 
	purple_accounts_set_ui_ops_wrapped(&accountUiOps);
 
	purple_notify_set_ui_ops_wrapped(&notifyUiOps);
 
	purple_request_set_ui_ops_wrapped(&requestUiOps);
 
	purple_xfers_set_ui_ops_wrapped(&xferUiOps);
 
	purple_connections_set_ui_ops_wrapped(&conn_ui_ops);
 
	purple_conversations_set_ui_ops_wrapped(&conversation_ui_ops);
 
	purple_roomlist_set_ui_ops_wrapped(&roomlist_ui_ops);
 
	
 
// #ifndef WIN32
 
// 	purple_dnsquery_set_ui_ops_wrapped(getDNSUiOps());
 
// #endif
 
}
 

	
 
/***** Core Ui Ops *****/
 
static void
 
spectrum_glib_log_handler(const gchar *domain,
 
						  GLogLevelFlags flags,
 
						  const gchar *message,
 
						  gpointer user_data)
 
{
 
	const char *level;
 
	char *new_msg = NULL;
 
	char *new_domain = NULL;
 

	
 
	if ((flags & G_LOG_LEVEL_ERROR) == G_LOG_LEVEL_ERROR)
 
		level = "ERROR";
 
	else if ((flags & G_LOG_LEVEL_CRITICAL) == G_LOG_LEVEL_CRITICAL)
 
		level = "CRITICAL";
 
	else if ((flags & G_LOG_LEVEL_WARNING) == G_LOG_LEVEL_WARNING)
 
		level = "WARNING";
 
	else if ((flags & G_LOG_LEVEL_MESSAGE) == G_LOG_LEVEL_MESSAGE)
 
		level = "MESSAGE";
 
	else if ((flags & G_LOG_LEVEL_INFO) == G_LOG_LEVEL_INFO)
 
		level = "INFO";
 
	else if ((flags & G_LOG_LEVEL_DEBUG) == G_LOG_LEVEL_DEBUG)
 
		level = "DEBUG";
 
	else {
 
		LOG4CXX_ERROR(logger, "Unknown glib logging level in " << (guint)flags);
 
		level = "UNKNOWN"; /* This will never happen. */
 
	}
 

	
 
	if (message != NULL)
 
		new_msg = purple_utf8_try_convert_wrapped(message);
 

	
 
	if (domain != NULL)
 
		new_domain = purple_utf8_try_convert_wrapped(domain);
 

	
 
	if (new_msg != NULL) {
 
		std::string area("glib");
 
		area.push_back('/');
 
		area.append(level);
 

	
 
		std::string message(new_domain ? new_domain : "g_log");
 
		message.push_back(' ');
 
		message.append(new_msg);
 

	
 
		LOG4CXX_ERROR(logger, message);
 
		g_free(new_msg);
 
	}
 

	
 
	g_free(new_domain);
 
}
 

	
 
static void
 
debug_init(void)
 
{
 
#define REGISTER_G_LOG_HANDLER(name) \
 
	g_log_set_handler((name), \
 
		(GLogLevelFlags)(G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL \
 
										  | G_LOG_FLAG_RECURSION), \
 
					  spectrum_glib_log_handler, NULL)
 

	
 
	REGISTER_G_LOG_HANDLER(NULL);
 
	REGISTER_G_LOG_HANDLER("GLib");
 
	REGISTER_G_LOG_HANDLER("GModule");
 
	REGISTER_G_LOG_HANDLER("GLib-GObject");
 
	REGISTER_G_LOG_HANDLER("GThread");
 
	REGISTER_G_LOG_HANDLER("GConf");
 
	
 

	
 
#undef REGISTER_G_LOD_HANDLER
 
}
 

	
 
static PurpleCoreUiOps coreUiOps =
 
{
 
	NULL,
 
	debug_init,
 
	transport_core_ui_init,
 
	NULL,
 
	spectrum_ui_get_info,
 
	NULL,
 
	NULL,
 
	NULL
 
};
 

	
 
static void signed_on(PurpleConnection *gc, gpointer unused) {
 
	PurpleAccount *account = purple_connection_get_account_wrapped(gc);
 
	np->handleConnected(np->m_accounts[account]);
 
#ifndef WIN32
 
#if !defined(__FreeBSD__) && !defined(__APPLE__)
 
	// force returning of memory chunks allocated by libxml2 to kernel
 
	malloc_trim(0);
 
#endif
 
#endif
 
	purple_roomlist_get_list_wrapped(gc);
 
	
 
	// For prpl-gg
 
	execute_purple_plugin_action(gc, "Download buddylist from Server");
 
}
 

	
 
static void printDebug(PurpleDebugLevel level, const char *category, const char *arg_s) {
 
	std::string c("");
 
	std::string args(arg_s);
 
	args.erase(args.size() - 1);
 

	
 
	if (category) {
 
		c.append(category);
 
	}
 

	
 
	c.push_back(':');
 

	
 
	LOG4CXX_INFO(logger_libpurple, c << args);
 
}
 

	
 
/*
 
 * Ops....
 
 */
 
static PurpleDebugUiOps debugUiOps =
 
{
 
	printDebug,
 
	NULL,
 
	NULL,
 
	NULL,
 
	NULL,
 
	NULL
 
};
 

	
 
static void buddyTyping(PurpleAccount *account, const char *who, gpointer null) {
 
	std::string w = purple_normalize_wrapped(account, who);
 
	size_t pos = w.find("/");
 
	if (pos != std::string::npos)
 
		w.erase((int) pos, w.length() - (int) pos);
 
	np->handleBuddyTyping(np->m_accounts[account], w);
 
}
 

	
 
static void buddyTyped(PurpleAccount *account, const char *who, gpointer null) {
 
	std::string w = purple_normalize_wrapped(account, who);
 
	size_t pos = w.find("/");
 
	if (pos != std::string::npos)
 
		w.erase((int) pos, w.length() - (int) pos);
 
	np->handleBuddyTyped(np->m_accounts[account], w);
 
}
 

	
 
static void buddyTypingStopped(PurpleAccount *account, const char *who, gpointer null){
 
	std::string w = purple_normalize_wrapped(account, who);
 
	size_t pos = w.find("/");
 
	if (pos != std::string::npos)
 
		w.erase((int) pos, w.length() - (int) pos);
 
	np->handleBuddyStoppedTyping(np->m_accounts[account], w);
 
}
 

	
 
static void gotAttention(PurpleAccount *account, const char *who, PurpleConversation *conv, guint type) {
 
	std::string w = purple_normalize_wrapped(account, who);
 
	size_t pos = w.find("/");
 
	if (pos != std::string::npos)
 
		w.erase((int) pos, w.length() - (int) pos);
 
	np->handleAttention(np->m_accounts[account], w, "");
 
}
 

	
 
static bool initPurple() {
 
	bool ret;
 

	
 
	std::string libPurpleDllPath = CONFIG_STRING_DEFAULTED(config, "purple.libpurple_dll_path", "");
 

	
 
	if (!resolvePurpleFunctions()) {
 
		LOG4CXX_ERROR(logger, "Unable to load libpurple.dll or some of the needed methods");
 
		return false;
 
	}
 

	
 
	std::string pluginsDir = CONFIG_STRING_DEFAULTED(config, "purple.plugins_dir", "./plugins");
 
	LOG4CXX_INFO(logger, "Setting libpurple plugins directory to: " << pluginsDir);
 
	purple_plugins_add_search_path_wrapped(pluginsDir.c_str());
 

	
 
	std::string cacertsDir = CONFIG_STRING_DEFAULTED(config, "purple.cacerts_dir", "./ca-certs");
 
	LOG4CXX_INFO(logger, "Setting libpurple cacerts directory to: " << cacertsDir);
 
	purple_certificate_add_ca_search_path_wrapped(cacertsDir.c_str());
 
 
 
	std::string userDir = CONFIG_STRING_DEFAULTED(config, "service.working_dir", "./");
 
	LOG4CXX_INFO(logger, "Setting libpurple user directory to: " << userDir);
 

	
 
	purple_util_set_user_dir_wrapped(userDir.c_str());
 
	remove("./accounts.xml");
 
	remove("./blist.xml");
 

	
 
	purple_debug_set_ui_ops_wrapped(&debugUiOps);
 
// 	purple_debug_set_verbose_wrapped(true);
 

	
 
	purple_core_set_ui_ops_wrapped(&coreUiOps);
 
	if (CONFIG_STRING_DEFAULTED(config, "service.eventloop", "") == "libev") {
 
		LOG4CXX_INFO(logger, "Will use libev based event loop");
 
	}
 
	else {
 
		LOG4CXX_INFO(logger, "Will use glib based event loop");
 
	}
 
	purple_eventloop_set_ui_ops_wrapped(getEventLoopUiOps(CONFIG_STRING_DEFAULTED(config, "service.eventloop", "") == "libev"));
 

	
 
	ret = purple_core_init_wrapped("spectrum");
 
	if (ret) {
 
		static int blist_handle;
 
		static int conversation_handle;
 

	
 
		purple_set_blist_wrapped(purple_blist_new_wrapped());
 
		purple_blist_load_wrapped();
 

	
 
		purple_prefs_load_wrapped();
 

	
 
		/* Good default preferences */
 
		/* The combination of these two settings mean that libpurple will never
 
		 * (of its own accord) set all the user accounts idle.
 
		 */
 
		purple_prefs_set_bool_wrapped("/purple/away/away_when_idle", false);
 
		/*
 
		 * This must be set to something not "none" for idle reporting to work
 
		 * for, e.g., the OSCAR prpl. We don't implement the UI ops, so this is
 
		 * okay for now.
 
		 */
 
		purple_prefs_set_string_wrapped("/purple/away/idle_reporting", "system");
 

	
 
		/* Disable all logging */
 
		purple_prefs_set_bool_wrapped("/purple/logging/log_ims", false);
 
		purple_prefs_set_bool_wrapped("/purple/logging/log_chats", false);
 
		purple_prefs_set_bool_wrapped("/purple/logging/log_system", false);
 

	
 

	
 
// 		purple_signal_connect_wrapped(purple_conversations_get_handle_wrapped(), "received-im-msg", &conversation_handle, PURPLE_CALLBACK(newMessageReceived), NULL);
 
		purple_signal_connect_wrapped(purple_conversations_get_handle_wrapped(), "buddy-typing", &conversation_handle, PURPLE_CALLBACK(buddyTyping), NULL);
 
		purple_signal_connect_wrapped(purple_conversations_get_handle_wrapped(), "buddy-typed", &conversation_handle, PURPLE_CALLBACK(buddyTyped), NULL);
 
		purple_signal_connect_wrapped(purple_conversations_get_handle_wrapped(), "buddy-typing-stopped", &conversation_handle, PURPLE_CALLBACK(buddyTypingStopped), NULL);
 
		purple_signal_connect_wrapped(purple_blist_get_handle_wrapped(), "buddy-privacy-changed", &conversation_handle, PURPLE_CALLBACK(buddyPrivacyChanged), NULL);
 
		purple_signal_connect_wrapped(purple_conversations_get_handle_wrapped(), "got-attention", &conversation_handle, PURPLE_CALLBACK(gotAttention), NULL);
 
		purple_signal_connect_wrapped(purple_connections_get_handle_wrapped(), "signed-on", &blist_handle,PURPLE_CALLBACK(signed_on), NULL);
 
// 		purple_signal_connect_wrapped(purple_blist_get_handle_wrapped(), "buddy-removed", &blist_handle,PURPLE_CALLBACK(buddyRemoved), NULL);
 
// 		purple_signal_connect_wrapped(purple_blist_get_handle_wrapped(), "buddy-signed-on", &blist_handle,PURPLE_CALLBACK(buddySignedOn), NULL);
 
// 		purple_signal_connect_wrapped(purple_blist_get_handle_wrapped(), "buddy-signed-off", &blist_handle,PURPLE_CALLBACK(buddySignedOff), NULL);
 
// 		purple_signal_connect_wrapped(purple_blist_get_handle_wrapped(), "buddy-status-changed", &blist_handle,PURPLE_CALLBACK(buddyStatusChanged), NULL);
 
		purple_signal_connect_wrapped(purple_blist_get_handle_wrapped(), "blist-node-removed", &blist_handle,PURPLE_CALLBACK(NodeRemoved), NULL);
 
		purple_signal_connect_wrapped(purple_conversations_get_handle_wrapped(), "chat-topic-changed", &conversation_handle, PURPLE_CALLBACK(conv_chat_topic_changed), NULL);
 
		static int xfer_handle;
 
		purple_signal_connect_wrapped(purple_xfers_get_handle_wrapped(), "file-send-start", &xfer_handle, PURPLE_CALLBACK(fileSendStart), NULL);
 
		purple_signal_connect_wrapped(purple_xfers_get_handle_wrapped(), "file-recv-start", &xfer_handle, PURPLE_CALLBACK(fileRecvStart), NULL);
 
		purple_signal_connect_wrapped(purple_xfers_get_handle_wrapped(), "file-recv-request", &xfer_handle, PURPLE_CALLBACK(newXfer), NULL);
 
		purple_signal_connect_wrapped(purple_xfers_get_handle_wrapped(), "file-recv-complete", &xfer_handle, PURPLE_CALLBACK(XferReceiveComplete), NULL);
 
		purple_signal_connect_wrapped(purple_xfers_get_handle_wrapped(), "file-send-complete", &xfer_handle, PURPLE_CALLBACK(XferSendComplete), NULL);
 
// 
 
// 		purple_commands_init();
 

	
 
	}
 
	return ret;
 
}
 

	
 

	
 
static void transportDataReceived(gpointer data, gint source, PurpleInputCondition cond) {
 
	if (cond & PURPLE_INPUT_READ) {
 
		char buffer[65535];
 
		char *ptr = buffer;
 
#ifdef WIN32
 
		ssize_t n = recv(source, ptr, sizeof(buffer), 0);
 
#else
 
		ssize_t n = read(source, ptr, sizeof(buffer));
 
#endif
 
		if (n <= 0) {
 
			if (errno == EAGAIN) {
 
				return;
 
			}
 
			LOG4CXX_INFO(logger, "Diconnecting from spectrum2 server");
 
			exit(errno);
 
		}
 
		std::string d = std::string(buffer, n);
 

	
 
		if (firstPing) {
 
			firstPing = false;
 
			NetworkPlugin::PluginConfig cfg;			
 
			cfg.setSupportMUC(true);
 
			if (CONFIG_STRING(config, "service.protocol") == "prpl-telegram") {
 
				cfg.setNeedPassword(false);
 
			}
 
			if (CONFIG_STRING(config, "service.protocol") != "prpl-irc") {
 
			if (CONFIG_STRING(config, "service.protocol") == "prpl-irc") {
 
				cfg.setNeedRegistration(false);
 
			}
 
			else {
 
				cfg.setNeedRegistration(true);
 
			}
 
			np->sendConfig(cfg);
 
		}
 

	
 
		np->handleDataRead(d);
 
	}
 
	else {
 
		if (writeInput != 0) {
 
			purple_input_remove_wrapped(writeInput);
 
			writeInput = 0;
 
		}
 
		np->readyForData();
 
	}
 
}
 

	
 
int main(int argc, char **argv) {
 
#ifndef WIN32
 
#if !defined(__FreeBSD__) && !defined(__APPLE__)
 
		mallopt(M_CHECK_ACTION, 2);
 
		mallopt(M_PERTURB, 0xb);
 
#endif
 

	
 
		signal(SIGPIPE, SIG_IGN);
 

	
 
		if (signal(SIGCHLD, spectrum_sigchld_handler) == SIG_ERR) {
 
			std::cout << "SIGCHLD handler can't be set\n";
 
			return -1;
 
		}
 
#endif
 

	
 
	std::string error;
 
	Config *cfg = Config::createFromArgs(argc, argv, error, host, port);
 
	if (cfg == NULL) {
 
		std::cerr << error;
 
		return 1;
 
	}
 

	
 
	config = boost::shared_ptr<Config>(cfg);
 
 
 
	Logging::initBackendLogging(config.get());
 
	initPurple();
 
 
 
	main_socket = create_socket(host.c_str(), port);
 
	purple_input_add_wrapped(main_socket, PURPLE_INPUT_READ, &transportDataReceived, NULL);
 
	purple_timeout_add_seconds_wrapped(30, pingTimeout, NULL);
 
 
 
	np = new SpectrumNetworkPlugin();
 
	bool libev = CONFIG_STRING_DEFAULTED(config, "service.eventloop", "") == "libev";
 

	
 
	GMainLoop *m_loop;
 
#ifdef WITH_LIBEVENT
 
	if (!libev) {
 
		m_loop = g_main_loop_new(NULL, FALSE);
 
	}
 
	else {
 
		event_init();
 
	}
 
#endif
 
	m_loop = g_main_loop_new(NULL, FALSE);
 
	if (m_loop) {
 
		g_main_loop_run(m_loop);
 
	}
 
#ifdef WITH_LIBEVENT
 
	else {
 
		event_loop(0);
 
	}
 
#endif
 

	
 
	return 0;
 
}
include/transport/WebSocketClient.h
Show inline comments
 
/**
 
 * 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 <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/Version.h"
 
#include "Swiften/Network/Timer.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 Component;
 

	
 
class WebSocketClient {
 
	public:
 
		WebSocketClient(Component *component, const std::string &user);
 

	
 
		virtual ~WebSocketClient();
 

	
 
		void connectServer(const std::string &u);
 
		void disconnectServer();
 

	
 
		void write(const std::string &data);
 

	
 
		boost::signal<void (const std::string &payload)> onPayloadReceived;
 

	
 
		boost::signal<void ()> onWebSocketConnected;
 
		boost::signal<void (const boost::optional<Swift::Connection::Error> &error)> onWebSocketDisconnected;
 

	
 
	private:
 
		void handleDNSResult(const std::vector<Swift::HostAddress>&, boost::optional<Swift::DomainNameResolveError>);
 
		void handleDataRead(boost::shared_ptr<Swift::SafeByteArray> data);
 
		void handleConnected(bool error);
 
		void handleDisconnected(const boost::optional<Swift::Connection::Error> &error);
 

	
 
		void connectServer();
 

	
 
	private:
 
		Component *m_component;
 
		boost::shared_ptr<Swift::DomainNameAddressQuery> m_dnsQuery;
 
		boost::shared_ptr<Swift::Connection> m_conn;
 
		Swift::TLSConnectionFactory *m_tlsConnectionFactory;
 
		Swift::PlatformTLSFactories *m_tlsFactory;
 
		std::string m_host;
 
		std::string m_path;
 
		std::string m_buffer;
 
		bool m_upgraded;
 
		Swift::Timer::ref m_reconnectTimer;
 
		std::string m_user;
 
};
 

	
 
}
libtransport/WebSocketClient.cpp
Show inline comments
 
/**
 
 * 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 "transport/WebSocketClient.h"
 
#include "transport/Transport.h"
 
#include "transport/Util.h"
 
#include "transport/Logging.h"
 

	
 
#include "Swiften/StringCodecs/Hexify.h"
 

	
 
#include <boost/foreach.hpp>
 
#include <boost/make_shared.hpp>
 
#include <map>
 
#include <iterator>
 

	
 
namespace Transport {
 

	
 
DEFINE_LOGGER(logger, "WebSocketClient");
 

	
 
WebSocketClient::WebSocketClient(Component *component, const std::string &user) {
 
	m_component = component;
 
	m_upgraded = false;
 
	m_user = user;
 

	
 
#if HAVE_SWIFTEN_3
 
	Swift::TLSOptions o;
 
#endif
 
	m_tlsFactory = new Swift::PlatformTLSFactories();
 
#if HAVE_SWIFTEN_3
 
	m_tlsConnectionFactory = new Swift::TLSConnectionFactory(m_tlsFactory->getTLSContextFactory(), component->getNetworkFactories()->getConnectionFactory(), o);
 
#else
 
	m_tlsConnectionFactory = new Swift::TLSConnectionFactory(m_tlsFactory->getTLSContextFactory(), component->getNetworkFactories()->getConnectionFactory());
 
#endif
 

	
 
	m_reconnectTimer = m_component->getNetworkFactories()->getTimerFactory()->createTimer(1000);
 
	m_reconnectTimer->onTick.connect(boost::bind(&WebSocketClient::connectServer, this));
 
}
 

	
 
WebSocketClient::~WebSocketClient() {
 
	if (m_conn) {
 
		m_conn->onDataRead.disconnect(boost::bind(&WebSocketClient::handleDataRead, this, _1));
 
		m_conn->disconnect();
 
	}
 

	
 
	delete m_tlsFactory;
 
	delete m_tlsConnectionFactory;
 
}
 

	
 
void WebSocketClient::connectServer() {
 
	LOG4CXX_INFO(logger, m_user << ": Starting DNS query for " << m_host << " " << m_path);
 

	
 
	m_upgraded = false;
 
	m_buffer.clear();
 

	
 
	m_dnsQuery = m_component->getNetworkFactories()->getDomainNameResolver()->createAddressQuery(m_host);
 
	m_dnsQuery->onResult.connect(boost::bind(&WebSocketClient::handleDNSResult, this, _1, _2));
 
	m_dnsQuery->run();
 
	m_reconnectTimer->stop();
 
}
 

	
 
void WebSocketClient::connectServer(const std::string &url) {
 
	std::string u = url.substr(6);
 
	m_host = u.substr(0, u.find("/"));
 
	m_path = u.substr(u.find("/"));
 
	connectServer();
 
}
 

	
 
void WebSocketClient::disconnectServer() {
 
	if (m_conn) {
 
		m_conn->onDataRead.disconnect(boost::bind(&WebSocketClient::handleDataRead, this, _1));
 
		m_conn->disconnect();
 
	}
 
}
 

	
 
void WebSocketClient::write(const std::string &data) {
 
	if (!m_conn) {
 
		return;
 
	}
 

	
 
	uint8_t opcode = 129; // UTF8
 
	if (data.empty()) {
 
		opcode = 138; // PONG
 
	}
 

	
 
	// Mask the payload
 
	char mask_bits[4] = {0x11, 0x22, 0x33, 0x44};
 
	std::string payload = data;
 
	for (size_t i = 0; i < data.size(); i++ ) {
 
		payload[i] = payload[i] ^ mask_bits[i&3];
 
	}
 

	
 
	if (data.size() <= 125) {
 
		uint8_t size7 = data.size() + 128; // Mask bit
 
		m_conn->write(Swift::createSafeByteArray(std::string((char *) &opcode, 1)
 
			+ std::string((char *) &size7, 1)
 
			+ std::string((char *) &mask_bits[0], 4)
 
			+ payload));
 
	}
 
	else {
 
		uint8_t size7 = 126 + 128; // Mask bit
 
		uint16_t size16 = data.size();
 
		size16 = htons(size16);
 
		m_conn->write(Swift::createSafeByteArray(std::string((char *) &opcode, 1)
 
			+ std::string((char *) &size7, 1)
 
			+ std::string((char *) &size16, 2)
 
			+ std::string((char *) &mask_bits[0], 4)
 
			+ payload));
 
	}
 

	
 
	LOG4CXX_INFO(logger, m_user << ": > " << data);
 
}
 

	
 
void WebSocketClient::handleDataRead(boost::shared_ptr<Swift::SafeByteArray> data) {
 
	std::string d = Swift::safeByteArrayToString(*data);
 
	m_buffer += d;
 

	
 
	if (!m_upgraded) {
 
		if (m_buffer.find("\r\n\r\n") != std::string::npos) {
 
			m_buffer.erase(0, m_buffer.find("\r\n\r\n") + 4);
 
			m_upgraded = true;
 
			onWebSocketConnected();
 
		}
 
		else {
 
			return;
 
		}
 
	}
 

	
 
	while (m_buffer.size() > 0) {
 
		if (m_buffer.size() >= 2) {
 
			uint8_t opcode = *((uint8_t *) &m_buffer[0]) & 0xf;
 
			uint8_t size7 = *((uint8_t *) &m_buffer[1]) & 127;
 
			bool mask = *((uint8_t *) &m_buffer[1]) & 128;
 
			uint16_t size16 = 0;
 
			int header_size = 2;
 
			if (size7 == 126) {
 
				if (m_buffer.size() >= 4) {
 
					size16 = *((uint16_t *) &m_buffer[2]);
 
					size16 = ntohs(size16);
 
					header_size += 2;
 
				}
 
				else {
 
					return;
 
				}
 
			}
 

	
 
			// This seems to be Slack bug... sometimes we receive 0x89 followed by 0x81
 
			// For now, in that case we will just ignore the 0x89 and skip it...
 
			if (opcode == 9 && mask && size7 == 1) {
 
				LOG4CXX_WARN(logger, m_user << ": Applying Slack workaround because of partial data received from server");
 
				m_buffer.erase(0, 1);
 
				continue;
 
			}
 

	
 
			unsigned int size = (size16 == 0 ? size7 : size16);
 
			if (m_buffer.size() >= size + header_size) {
 
				std::string payload = m_buffer.substr(header_size, size);
 
				LOG4CXX_INFO(logger, m_user << ": < " << payload);
 
				onPayloadReceived(payload);
 
				m_buffer.erase(0, size + header_size);
 
				
 
			}
 
			else if (size == 0) {
 
				m_buffer.erase(0, header_size);
 
			}
 
			else {
 
				return;
 
			}
 
		}
 
		else {
 
			return;
 
		}
 
	}
 
}
 

	
 
void WebSocketClient::handleConnected(bool error) {
 
	if (error) {
 
		LOG4CXX_ERROR(logger, "Connection to " << m_host << " failed. Will reconnect in 1 second.");
 
		m_reconnectTimer->start();
 
		return;
 
	}
 

	
 
	LOG4CXX_INFO(logger, m_user << ": Connected to " << m_host);
 

	
 
	std::string req = "";
 
	req += "GET " + m_path + " HTTP/1.1\r\n";
 
	req += "Host: " + m_host + ":443\r\n";
 
	req += "Upgrade: websocket\r\n";
 
	req += "Connection: Upgrade\r\n";
 
	req += "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n";
 
	req += "Sec-WebSocket-Version: 13\r\n";
 
	req += "\r\n";
 

	
 
	m_conn->write(Swift::createSafeByteArray(req));
 
}
 

	
 
void WebSocketClient::handleDisconnected(const boost::optional<Swift::Connection::Error> &error) {
 
	if (!error) {
 
		return;
 
	}
 

	
 
	LOG4CXX_ERROR(logger, m_user << ": Disconected from " << m_host << ". Will reconnect in 1 second.");
 
	onWebSocketDisconnected(error);
 
	m_reconnectTimer->start();
 
}
 

	
 
void WebSocketClient::handleDNSResult(const std::vector<Swift::HostAddress> &addrs, boost::optional<Swift::DomainNameResolveError> error) {
 
	if (error) {
 
		LOG4CXX_ERROR(logger, m_user << ": DNS resolving error. Will try again in 1 second.");
 
		m_reconnectTimer->start();
 
		return;
 
	}
 

	
 
	if (addrs.empty()) {
 
		LOG4CXX_ERROR(logger, m_user << ": DNS name cannot be resolved. Will try again in 1 second.");
 
		m_reconnectTimer->start();
 
		return;
 
	}
 

	
 
	m_conn = m_tlsConnectionFactory->createConnection();
 
	m_conn->onDataRead.connect(boost::bind(&WebSocketClient::handleDataRead, this, _1));
 
	m_conn->onConnectFinished.connect(boost::bind(&WebSocketClient::handleConnected, this, _1));
 
	m_conn->onDisconnected.connect(boost::bind(&WebSocketClient::handleDisconnected, this, _1));
 
	m_conn->connect(Swift::HostAddressPort(addrs[0], 443));
 
}
 

	
 
}
spectrum/src/frontends/slack/SlackRTM.cpp
Show inline comments
 
/**
 
 * 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 "SlackRTM.h"
 
#include "SlackFrontend.h"
 
#include "SlackUser.h"
 
#include "SlackIdManager.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, "SlackRTM");
 

	
 
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_uinfo.jid);
 
	m_client->onPayloadReceived.connect(boost::bind(&SlackRTM::handlePayloadReceived, this, _1));
 
	m_client->onWebSocketConnected.connect(boost::bind(&SlackRTM::handleWebSocketConnected, this));
 

	
 
	m_pingTimer = m_component->getNetworkFactories()->getTimerFactory()->createTimer(20000);
 
	m_pingTimer->onTick.connect(boost::bind(&SlackRTM::sendPing, this));
 

	
 
	int type = (int) TYPE_STRING;
 
	m_storageBackend->getUserSetting(m_uinfo.id, "bot_token", type, m_token);
 

	
 
	m_api = new SlackAPI(component, m_idManager, m_token, m_uinfo.jid);
 
}
 

	
 
SlackRTM::~SlackRTM() {
 
	delete m_client;
 
	delete m_api;
 
	m_pingTimer->stop();
 
}
 

	
 
void SlackRTM::start() {
 
	std::string url = "https://slack.com/api/rtm.start?";
 
	url += "token=" + Util::urlencode(m_token);
 

	
 
	HTTPRequest *req = new HTTPRequest(THREAD_POOL(m_component), HTTPRequest::Get, url, boost::bind(&SlackRTM::handleRTMStart, this, _1, _2, _3, _4));
 
	req->execute();
 
}
 

	
 
#define STORE_STRING(FROM, NAME) rapidjson::Value &NAME##_tmp = FROM[#NAME]; \
 
	if (!NAME##_tmp.IsString()) {  \
 
		LOG4CXX_ERROR(logger, "No '" << #NAME << "' string in the reply."); \
 
		LOG4CXX_ERROR(logger, payload); \
 
		return; \
 
	} \
 
	std::string NAME = NAME##_tmp.GetString();
 

	
 
#define STORE_STRING_OPTIONAL(FROM, NAME) rapidjson::Value &NAME##_tmp = FROM[#NAME]; \
 
	std::string NAME; \
 
	if (NAME##_tmp.IsString()) {  \
 
		 NAME = NAME##_tmp.GetString(); \
 
	}
 

	
 
#define GET_OBJECT(FROM, NAME) rapidjson::Value &NAME = FROM[#NAME]; \
 
	if (!NAME.IsObject()) { \
 
		LOG4CXX_ERROR(logger, "No '" << #NAME << "' object in the reply."); \
 
		return; \
 
	}
 

	
 
#define STORE_INT(FROM, NAME) rapidjson::Value &NAME##_tmp = FROM[#NAME]; \
 
	if (!NAME##_tmp.IsInt()) {  \
 
		LOG4CXX_ERROR(logger, "No '" << #NAME << "' number in the reply."); \
 
		LOG4CXX_ERROR(logger, payload); \
 
		return; \
 
	} \
 
	int NAME = NAME##_tmp.GetInt();
 

	
 
void SlackRTM::handlePayloadReceived(const std::string &payload) {
 
	rapidjson::Document d;
 
	if (d.Parse<0>(payload.c_str()).HasParseError()) {
 
		LOG4CXX_ERROR(logger, "Error while parsing JSON");
 
		LOG4CXX_ERROR(logger, payload);
 
		return;
 
	}
 

	
 
	STORE_STRING(d, type);
 

	
 
	if (type == "message") {
 
		STORE_STRING(d, channel);
 
		STORE_STRING(d, text);
 
		STORE_STRING(d, ts);
 
		STORE_STRING_OPTIONAL(d, subtype);
 
		STORE_STRING_OPTIONAL(d, purpose);
 

	
 
		rapidjson::Value &attachments = d["attachments"];
 
		if (attachments.IsArray()) {
 
			for (int i = 0; i < attachments.Size(); i++) {
 
				STORE_STRING_OPTIONAL(attachments[i], fallback);
 
				if (!fallback.empty()) {
 
					text += fallback;
 
				}
 
			}
 
		}
 

	
 
		if (subtype == "bot_message") {
 
			STORE_STRING(d, bot_id);
 
			onMessageReceived(channel, bot_id, text, ts);
 
		}
 
		else if (subtype == "me_message") {
 
			text = "/me " + text;
 
			STORE_STRING(d, user);
 
			onMessageReceived(channel, user, text, ts);
 
		}
 
		else if (subtype == "channel_join") {
 
			
 
		}
 
		else if (!purpose.empty()) {
 
			
 
		}
 
		else {
 
			STORE_STRING(d, user);
 
			onMessageReceived(channel, user, text, ts);
 
		}
 
	}
 
	else if (type == "channel_joined"
 
		  || type == "channel_created") {
 
		std::map<std::string, SlackChannelInfo> &channels = m_idManager->getChannels();
 
		SlackAPI::getSlackChannelInfo(NULL, true, d, payload, channels);
 
	}
 
	else if (type == "error") {
 
		GET_OBJECT(d, error);
 
		STORE_INT(error, code);
 

	
 
		if (code == 1) {
 
			LOG4CXX_INFO(logger, "Reconnecting to Slack network");
 
			m_pingTimer->stop();
 
			m_client->disconnectServer();
 
			start();
 
		}
 
	}
 
}
 

	
 
void SlackRTM::sendMessage(const std::string &channel, const std::string &message) {
 
	m_counter++;
 

	
 
	std::string m = message;
 
	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();
 
}
 

	
 
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_idManager->setSelfName(selfName.GetString());
 

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

	
 
	m_idManager->setSelfId(selfId.GetString());
 

	
 
	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);
 
	LOG4CXX_INFO(logger, data);
 

	
 
#ifndef LIBTRANSPORT_TEST
 
	m_client->connectServer(u);
 
#endif
 
}
 

	
 
void SlackRTM::handleWebSocketConnected() {
 
	if (!m_started) {
 
		onRTMStarted();
 
		m_started = true;
 
	}
 

	
 
	m_pingTimer->start();
 
}
 

	
 
void SlackRTM::handleWebSocketDisconnected(const boost::optional<Swift::Connection::Error> &error) {
 
	m_pingTimer->stop();
 
}
 

	
 

	
 
}
spectrum/src/frontends/slack/SlackSession.cpp
Show inline comments
 
/**
 
 * 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 "SlackSession.h"
 
#include "SlackFrontend.h"
 
#include "SlackUser.h"
 
#include "SlackRTM.h"
 
#include "SlackRosterManager.h"
 
#include "SlackIdManager.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 <Swiften/Version.h>
 
#define HAVE_SWIFTEN_3  (SWIFTEN_VERSION >= 0x030000)
 

	
 
#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_disconnected(false) {
 
	m_component = component;
 
	m_storageBackend = storageBackend;
 

	
 
	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));
 
	m_rtm->start();
 

	
 
	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, m_idManager, token, m_uinfo.jid);
 

	
 
	LOG4CXX_INFO(logger, m_uinfo.jid << ": SlackSession created.");
 
}
 

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

	
 
	LOG4CXX_INFO(logger, m_uinfo.jid << ": SlackSession destroyed.");
 
}
 

	
 
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::sendMessageToAll(const std::string &msg) {
 
	std::vector<std::string> channels;
 
	for (std::map<std::string, std::string>::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("Spectrum 2", it->second, msg);
 
		}
 
	}
 
}
 

	
 
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()];
 
	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, m_uinfo.jid << "Sending message to Slack channel " << channel << " from " << from);
 
#if HAVE_SWIFTEN_3
 
	std::string body = message->getBody().get_value_or("");
 
#else
 
	std::string body = message->getBody();
 
#endif
 
	m_rtm->getAPI()->sendMessage(from, channel, body);
 
}
 

	
 
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, m_uinfo.jid << ": Setting channel purppose: " << ch << " " << purpose);
 
	m_api->setPurpose(ch, purpose);
 
}
 

	
 
void SlackSession::handleJoinRoomCreated(const std::string &channelId, std::vector<std::string> args) {
 
	args[5] = channelId;
 
	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);
 
// 	}
 

	
 
	LOG4CXX_INFO(logger, m_uinfo.jid << ": Channel " << args[5] << " is created. Joining the room on legacy network.");
 

	
 
	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<Swift::Payload>(new Swift::MUCPayload()));
 
	m_component->getFrontend()->onPresenceReceived(presence);
 

	
 
	m_onlineBuddiesTimer->start();
 
}
 

	
 
void SlackSession::handleJoinMessage(const std::string &message, std::vector<std::string> &args, bool quiet) {
 
	if (args[5][0] == '#') {
 
		args[5].erase(0, 1);
 
	}
 
	LOG4CXX_INFO(logger, m_uinfo.jid << ": Going to join the room " << args[3] << "@" << args[4] << ", transporting it to channel " << args[5]);
 
	m_api->createChannel(args[5], m_idManager->getSelfId(), boost::bind(&SlackSession::handleJoinRoomCreated, this, _1, args));
 
}
 

	
 
void SlackSession::handleSlackChannelCreated(const std::string &channelId) {
 
	m_slackChannel = channelId;
 

	
 
	LOG4CXX_INFO(logger, m_uinfo.jid << ": Main Slack Channel created, connecting the legacy network");
 
	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<Swift::Payload>(new Swift::MUCPayload()));
 
	m_component->getFrontend()->onPresenceReceived(presence);
 
}
 

	
 
void SlackSession::leaveRoom(const std::string &channel) {
 
void SlackSession::leaveRoom(const std::string &channel_) {
 
	std::string channel = channel_;
 
	if (channel[0] == '#') {
 
		channel.erase(0, 1);
 
	}
 
	std::string channelId = m_idManager->getId(channel);
 
	std::string to = m_channel2jid[channelId];
 
	if (to.empty()) {
 
		LOG4CXX_ERROR(logger, "Spectrum 2 is not configured to transport this Slack channel.")
 
		return;
 
	}
 

	
 
	LOG4CXX_INFO(logger, m_uinfo.jid << ": Leaving the legacy network room " << to);
 

	
 
	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<Swift::Payload>(new Swift::MUCPayload()));
 
	m_component->getFrontend()->onPresenceReceived(presence);
 
}
 

	
 
void SlackSession::handleMessageReceived(const std::string &channel, const std::string &user, const std::string &message, const std::string &ts, bool quiet) {
 
	std::string to = m_channel2jid[channel];
 
	if (m_idManager->getName(user) == m_idManager->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("<" + m_idManager->getName(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<Swift::Message> msg(new Swift::Message());
 
			msg->setTo(b->getJID());
 
			msg->setFrom(Swift::JID(m_uinfo.jid + "/default"));
 
			msg->setBody("<" + m_idManager->getName(user) + "> " + message);
 
			m_component->getFrontend()->onMessageReceived(msg);
 
		}
 
	}
 
}
 

	
 
void SlackSession::handleDisconnected() {
 
	m_disconnected = true;
 
}
 

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

	
 

	
 
void SlackSession::handleConnected() {
 
	if (m_disconnected) {
 
		handleRTMStarted();
 
		m_disconnected = false;
 
	}
 
}
 

	
 
void SlackSession::handleRTMStarted() {
 
	std::string rooms = "";
 
	int type = (int) TYPE_STRING;
 
	m_storageBackend->getUserSetting(m_uinfo.id, "rooms", type, rooms);
 

	
 
	m_storageBackend->getUserSetting(m_uinfo.id, "slack_channel", type, m_slackChannel);
 
	if (!m_slackChannel.empty()) {
 
		m_api->createChannel(m_slackChannel, m_idManager->getSelfId(), boost::bind(&SlackSession::handleSlackChannelCreated, this, _1));
 
	}
 
	else {
 
		LOG4CXX_WARN(logger, m_uinfo.jid << ": There is no Main Slack Channel set for this user.");
 
	}
 

	
 
	// Auto-join the rooms configured by the Slack channel owner.
 
	if (!rooms.empty()) {
 
		std::vector<std::string> commands;
 
		boost::split(commands, rooms, boost::is_any_of("\n"));
 

	
 
		BOOST_FOREACH(const std::string &command, commands) {
 
			if (command.size() > 5) {
 
				std::vector<std::string> args;
 
				boost::split(args, command, boost::is_any_of(" "));
 
				handleJoinMessage("", args, false);
 
			}
 
		}
 
	}
 
}
 

	
 

	
 
}
spectrum/src/frontends/slack/SlackUserRegistration.cpp
Show inline comments
 
/**
 
 * libtransport -- C++ library for easy XMPP Transports development
 
 *
 
 * Copyright (C) 2011, 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
 
 */
 

	
 
#include "SlackUserRegistration.h"
 
#include "SlackRosterManager.h"
 
#include "SlackFrontend.h"
 

	
 
#include "transport/UserManager.h"
 
#include "transport/StorageBackend.h"
 
#include "transport/Transport.h"
 
#include "transport/RosterManager.h"
 
#include "transport/User.h"
 
#include "transport/Logging.h"
 
#include "transport/Buddy.h"
 
#include "transport/Config.h"
 
#include "transport/OAuth2.h"
 
#include "transport/Util.h"
 
#include "transport/HTTPRequest.h"
 

	
 
#include "rapidjson/document.h"
 

	
 
#include <boost/shared_ptr.hpp>
 
#include <boost/thread.hpp>
 
#include <boost/date_time/posix_time/posix_time.hpp>
 
#include <boost/regex.hpp> 
 

	
 
using namespace Swift;
 

	
 
namespace Transport {
 

	
 
DEFINE_LOGGER(logger, "SlackUserRegistration");
 

	
 
SlackUserRegistration::SlackUserRegistration(Component *component, UserManager *userManager,
 
								   StorageBackend *storageBackend)
 
: UserRegistration(component, userManager, storageBackend) {
 
	m_component = component;
 
	m_config = m_component->getConfig();
 
	m_storageBackend = storageBackend;
 
	m_userManager = userManager;
 

	
 
}
 

	
 
SlackUserRegistration::~SlackUserRegistration(){
 
	
 
}
 

	
 
std::string SlackUserRegistration::createOAuth2URL(const std::vector<std::string> &args) {
 
	std::string redirect_url = "https://slack.spectrum.im/oauth2/" + CONFIG_STRING(m_config, "service.jid");
 
	OAuth2 *oauth2 = new OAuth2(CONFIG_STRING_DEFAULTED(m_config, "service.client_id",""),
 
						  CONFIG_STRING_DEFAULTED(m_config, "service.client_secret",""),
 
						  "https://slack.com/oauth/authorize",
 
						  "https://slack.com/api/oauth.access",
 
						  redirect_url,
 
						  "channels:read channels:write team:read im:read im:write chat:write:bot bot");
 
	std::string url = oauth2->generateAuthURL();
 

	
 
	m_auths[oauth2->getState()] = oauth2;
 
	m_authsData[oauth2->getState()] = args;
 

	
 
	if (args.size() >= 3) {
 
		LOG4CXX_INFO(logger, "Generating OAUth2 URL with slack_channel=" << args[0] << ", 3rd_party_account=" << args[1]);
 
		LOG4CXX_INFO(logger, "Generating OAUth2 URL with slack_channel=" << args[1] << ", 3rd_party_account=" << args[2]);
 
	}
 
	else {
 
		LOG4CXX_WARN(logger, "Generating OAUth2 URL with too few arguments");
 
	}
 

	
 
	return url;
 
}
 

	
 
std::string SlackUserRegistration::getTeamDomain(const std::string &token) {
 
	std::string url = "https://slack.com/api/team.info?token=" + Util::urlencode(token);
 

	
 
	rapidjson::Document resp;
 
	HTTPRequest req(HTTPRequest::Get, url);
 
	if (!req.execute(resp)) {
 
		LOG4CXX_ERROR(logger, url);
 
		LOG4CXX_ERROR(logger, req.getError());
 
		return "";
 
	}
 

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

	
 
	rapidjson::Value &domain = team["domain"];
 
	if (!domain.IsString()) {
 
		LOG4CXX_ERROR(logger, "No 'domain' string in the reply.");
 
		LOG4CXX_ERROR(logger, url);
 
		LOG4CXX_ERROR(logger, req.getRawData());
 
		return "";
 
	}
 

	
 
	return domain.GetString();
 
}
 

	
 
std::string SlackUserRegistration::handleOAuth2Code(const std::string &code, const std::string &state) {
 
	OAuth2 *oauth2 = NULL;
 
	std::string token;
 
	std::string access_token;
 
	std::vector<std::string> data;
 
	std::string value;
 
	int type = (int) TYPE_STRING;
 

	
 
	if (state == "use_bot_token") {
 
		token = code;
 
		access_token = code;
 
	}
 
	else {
 
		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 error = oauth2->requestToken(code, access_token, token);
 
		if (!error.empty())  {
 
			return error;
 
		}
 

	
 
		if (token.empty()) {
 
			LOG4CXX_INFO(logger, "Using 'token' as 'bot_access_token'");
 
			token = access_token;
 
		}
 
	}
 

	
 
	std::string domain = getTeamDomain(access_token);
 
	if (domain.empty()) {
 
		return "The token you have provided is invalid";
 
	}
 

	
 
	std::string slackChannel;
 
	std::string uin;
 
	std::string password;
 
	if (data.size() >= 3) {
 
		slackChannel = data[1];
 
		uin = data[2];
 
	}
 
	if (data.size() == 4) {
 
		password = data[3];
 
	}
 

	
 
	UserInfo user;
 
	user.uin = "";
 
	user.password = "";
 
	user.id = 0;
 
	m_storageBackend->getUser(domain, user);
 

	
 
	user.jid = domain;
 
	user.uin = uin;
 
	user.password = password;
 
	user.language = "en";
 
	user.encoding = "";
 
	user.vip = 0;
 

	
 
	registerUser(user, true);
 

	
 
	m_storageBackend->getUser(user.jid, user);
 

	
 
	if (!slackChannel.empty()) {
 
		if (slackChannel[0] == '#') {
 
			slackChannel.erase(0, 1);
 
		}
 
		m_storageBackend->getUserSetting(user.id, "slack_channel", type, slackChannel);
 
	}
 

	
 
	value = token;
 
	m_storageBackend->getUserSetting(user.id, "bot_token", type, value);
 

	
 
	value = access_token;
 
	m_storageBackend->getUserSetting(user.id, "access_token", type, value);
 

	
 
	LOG4CXX_INFO(logger, "Registered Slack user " << user.jid << ", slack_channel=" << slackChannel);
 

	
 
	if (oauth2) {
 
		m_auths.erase(state);
 
		delete oauth2;
 

	
 
		m_authsData.erase(state);
 
	}
 

	
 
	m_component->getFrontend()->reconnectUser(user.jid);
 

	
 
	return "Registered as " + domain;
 
}
 

	
 
bool SlackUserRegistration::doUserRegistration(const UserInfo &row) {
 
	return true;
 
}
 

	
 
bool SlackUserRegistration::doUserUnregistration(const UserInfo &row) {
 
	return true;
 
}
 

	
 

	
 

	
 
}
0 comments (0 inline, 0 general)