facebook: implemented group chat support facebook

Thu, 11 Jun 2015 01:33:37 -0400

author
James Geboski <jgeboski@gmail.com>
date
Thu, 11 Jun 2015 01:33:37 -0400
branch
facebook
changeset 37258
291b6e1acc24
parent 37257
5ba291680ac4
child 37259
40b7fc17eab5

facebook: implemented group chat support

This is a fairly comprehensive implementation of the group chats which
are provided by the Messenger protocol. However, this does leave some
features out, such as removing users from a chat, as well as removing
oneself from a chat. This also does not support the inviting of users
which are not in the friend list of the user.

libpurple/protocols/facebook/Makefile.am file | annotate | diff | comparison | revisions
libpurple/protocols/facebook/Makefile.mingw file | annotate | diff | comparison | revisions
libpurple/protocols/facebook/api.c file | annotate | diff | comparison | revisions
libpurple/protocols/facebook/api.h file | annotate | diff | comparison | revisions
libpurple/protocols/facebook/data.c file | annotate | diff | comparison | revisions
libpurple/protocols/facebook/data.h file | annotate | diff | comparison | revisions
libpurple/protocols/facebook/facebook.c file | annotate | diff | comparison | revisions
libpurple/protocols/facebook/id.h file | annotate | diff | comparison | revisions
libpurple/protocols/facebook/util.c file | annotate | diff | comparison | revisions
libpurple/protocols/facebook/util.h file | annotate | diff | comparison | revisions
--- a/libpurple/protocols/facebook/Makefile.am	Sat Jun 06 02:13:52 2015 -0400
+++ b/libpurple/protocols/facebook/Makefile.am	Thu Jun 11 01:33:37 2015 -0400
@@ -9,6 +9,8 @@
 	marshal.h \
 	api.c \
 	api.h \
+	data.c \
+	data.h \
 	facebook.h \
 	facebook.c \
 	http.c \
--- a/libpurple/protocols/facebook/Makefile.mingw	Sat Jun 06 02:13:52 2015 -0400
+++ b/libpurple/protocols/facebook/Makefile.mingw	Thu Jun 11 01:33:37 2015 -0400
@@ -43,6 +43,7 @@
 ##
 C_SRC =	\
 			api.c \
+			data.c \
 			facebook.c \
 			http.c \
 			json.c \
--- a/libpurple/protocols/facebook/api.c	Sat Jun 06 02:13:52 2015 -0400
+++ b/libpurple/protocols/facebook/api.c	Thu Jun 11 01:33:37 2015 -0400
@@ -1180,7 +1180,7 @@
 	};
 
 	g_return_if_fail(FB_IS_API(api));
-	g_warn_if_fail((uids != NULL) && (uids->next != NULL));
+	g_warn_if_fail(g_slist_length(uids) > 1);
 	priv = api->priv;
 
 	bldr = fb_json_bldr_new(JSON_NODE_ARRAY);
@@ -1200,7 +1200,7 @@
 	json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL);
 	prms = fb_http_params_new();
 	fb_http_params_set_str(prms, "to", json);
-	fb_api_http_req(api, &info, prms, FB_API_URL_FQL);
+	fb_api_http_req(api, &info, prms, FB_API_URL_THRDS);
 	g_free(json);
 }
 
@@ -1404,6 +1404,11 @@
 		g_list_free(elms2);
 		elms2 = NULL;
 
+		if (g_slist_length(thrd.users) < 2) {
+			g_slist_free_full(thrd.users, g_free);
+			continue;
+		}
+
 		mptr = g_memdup(&thrd, sizeof thrd);
 		thrds = g_slist_prepend(thrds, mptr);
 	}
@@ -1419,7 +1424,7 @@
 }
 
 void
-fb_api_thread_list(FbApi *api, guint limit)
+fb_api_thread_list(FbApi *api)
 {
 	FbHttpParams *prms;
 	gchar *json;
@@ -1437,9 +1442,7 @@
 		"SELECT thread_fbid, participants, name "
 			"FROM unified_thread "
 			"WHERE folder='inbox' "
-			"ORDER BY timestamp DESC "
-			"LIMIT %u",
-		limit);
+			"ORDER BY timestamp DESC");
 	json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
 
 	prms = fb_http_params_new();
--- a/libpurple/protocols/facebook/api.h	Sat Jun 06 02:13:52 2015 -0400
+++ b/libpurple/protocols/facebook/api.h	Thu Jun 11 01:33:37 2015 -0400
@@ -189,7 +189,7 @@
 fb_api_thread_invite(FbApi *api, FbId tid, FbId uid);
 
 void
-fb_api_thread_list(FbApi *api, guint limit);
+fb_api_thread_list(FbApi *api);
 
 void
 fb_api_thread_topic(FbApi *api, FbId tid, const gchar *topic);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/facebook/data.c	Thu Jun 11 01:33:37 2015 -0400
@@ -0,0 +1,245 @@
+/* purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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 <string.h>
+
+#include "api.h"
+#include "data.h"
+
+struct _FbDataPrivate
+{
+	FbApi *api;
+	PurpleConnection *gc;
+	PurpleRoomlist *roomlist;
+	gint chatid;
+};
+
+static const gchar *fb_props_strs[] = {
+	"cid",
+	"did",
+	"stoken",
+	"token"
+};
+
+G_DEFINE_TYPE(FbData, fb_data, G_TYPE_OBJECT);
+
+static void
+fb_data_dispose(GObject *obj)
+{
+	FbDataPrivate *priv = FB_DATA(obj)->priv;
+
+	if (G_LIKELY(priv->api != NULL)) {
+		g_object_unref(priv->api);
+	}
+
+	if (priv->roomlist != NULL) {
+		g_object_unref(priv->roomlist);
+	}
+}
+
+static void
+fb_data_class_init(FbDataClass *klass)
+{
+	GObjectClass *gklass = G_OBJECT_CLASS(klass);
+
+	gklass->dispose = fb_data_dispose;
+	g_type_class_add_private(klass, sizeof (FbDataPrivate));
+}
+
+static void
+fb_data_init(FbData *fata)
+{
+	FbDataPrivate *priv;
+
+	priv = G_TYPE_INSTANCE_GET_PRIVATE(fata, FB_TYPE_DATA, FbDataPrivate);
+	fata->priv = priv;
+}
+
+FbData *
+fb_data_new(PurpleConnection *gc)
+{
+	FbData *fata;
+	FbDataPrivate *priv;
+
+	fata = g_object_new(FB_TYPE_DATA, NULL);
+	priv = fata->priv;
+
+	priv->api = fb_api_new(gc);
+	priv->gc = gc;
+
+	return fata;
+}
+
+gboolean
+fb_data_load(FbData *fata)
+{
+	const gchar *str;
+	FbDataPrivate *priv;
+	FbId id;
+	gboolean ret = TRUE;
+	guint i;
+	guint64 uint;
+	GValue val = G_VALUE_INIT;
+	PurpleAccount *acct;
+
+	g_return_val_if_fail(FB_IS_DATA(fata), FALSE);
+	priv = fata->priv;
+	acct = purple_connection_get_account(priv->gc);
+
+	for (i = 0; i < G_N_ELEMENTS(fb_props_strs); i++) {
+		str = purple_account_get_string(acct, fb_props_strs[i], NULL);
+
+		if (str == NULL) {
+			ret = FALSE;
+		}
+
+		g_value_init(&val, G_TYPE_STRING);
+		g_value_set_string(&val, str);
+		g_object_set_property(G_OBJECT(priv->api), fb_props_strs[i],
+		                      &val);
+		g_value_unset(&val);
+	}
+
+	str = purple_account_get_string(acct, "mid", NULL);
+
+	if (str != NULL) {
+		uint = g_ascii_strtoull(str, NULL, 10);
+		g_value_init(&val, G_TYPE_UINT64);
+		g_value_set_uint64(&val, uint);
+		g_object_set_property(G_OBJECT(priv->api), "mid", &val);
+		g_value_unset(&val);
+	} else {
+		ret = FALSE;
+	}
+
+	str = purple_account_get_string(acct, "uid", NULL);
+
+	if (str != NULL) {
+		id = FB_ID_FROM_STR(str);
+		g_value_init(&val, FB_TYPE_ID);
+		g_value_set_int64(&val, id);
+		g_object_set_property(G_OBJECT(priv->api), "uid", &val);
+		g_value_unset(&val);
+	} else {
+		ret = FALSE;
+	}
+
+	fb_api_rehash(priv->api);
+	return ret;
+}
+
+void
+fb_data_save(FbData *fata)
+{
+	const gchar *str;
+	FbDataPrivate *priv;
+	gchar *dup;
+	guint i;
+	guint64 uint;
+	GValue val = G_VALUE_INIT;
+	PurpleAccount *acct;
+
+	g_return_if_fail(FB_IS_DATA(fata));
+	priv = fata->priv;
+	acct = purple_connection_get_account(priv->gc);
+
+	for (i = 0; i < G_N_ELEMENTS(fb_props_strs); i++) {
+		g_value_init(&val, G_TYPE_STRING);
+		g_object_get_property(G_OBJECT(priv->api), fb_props_strs[i],
+		                      &val);
+		str = g_value_get_string(&val);
+		purple_account_set_string(acct, fb_props_strs[i], str);
+		g_value_unset(&val);
+	}
+
+	g_value_init(&val, G_TYPE_UINT64);
+	g_object_get_property(G_OBJECT(priv->api), "mid", &val);
+	uint = g_value_get_uint64(&val);
+	g_value_unset(&val);
+
+	dup = g_strdup_printf("%" G_GINT64_FORMAT, uint);
+	purple_account_set_string(acct, "mid", dup);
+	g_free(dup);
+
+	g_value_init(&val, G_TYPE_INT64);
+	g_object_get_property(G_OBJECT(priv->api), "uid", &val);
+	uint = g_value_get_int64(&val);
+	g_value_unset(&val);
+
+	dup = g_strdup_printf("%" FB_ID_FORMAT, uint);
+	purple_account_set_string(acct, "uid", dup);
+	g_free(dup);
+}
+
+FbApi *
+fb_data_get_api(FbData *fata)
+{
+	FbDataPrivate *priv;
+
+	g_return_val_if_fail(FB_IS_DATA(fata), NULL);
+	priv = fata->priv;
+
+	return priv->api;
+}
+
+gint
+fb_data_get_chatid(FbData *fata)
+{
+	FbDataPrivate *priv;
+
+	g_return_val_if_fail(FB_IS_DATA(fata), NULL);
+	priv = fata->priv;
+
+	return priv->chatid++;
+}
+
+PurpleConnection *
+fb_data_get_connection(FbData *fata)
+{
+	FbDataPrivate *priv;
+
+	g_return_val_if_fail(FB_IS_DATA(fata), NULL);
+	priv = fata->priv;
+
+	return priv->gc;
+}
+
+PurpleRoomlist *
+fb_data_get_roomlist(FbData *fata)
+{
+	FbDataPrivate *priv;
+
+	g_return_val_if_fail(FB_IS_DATA(fata), NULL);
+	priv = fata->priv;
+
+	return priv->roomlist;
+}
+
+void
+fb_data_set_roomlist(FbData *fata, PurpleRoomlist *list)
+{
+	FbDataPrivate *priv;
+
+	g_return_if_fail(FB_IS_DATA(fata));
+	priv = fata->priv;
+
+	priv->roomlist = list;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/facebook/data.h	Thu Jun 11 01:33:37 2015 -0400
@@ -0,0 +1,81 @@
+/* purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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
+ */
+
+#ifndef _FACEBOOK_DATA_H_
+#define _FACEBOOK_DATA_H_
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "connection.h"
+
+#define FB_TYPE_DATA             (fb_data_get_type())
+#define FB_DATA(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_DATA, FbData))
+#define FB_DATA(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_DATA, FbData))
+#define FB_DATA_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST((klass), FB_TYPE_DATA, FbDataClass))
+#define FB_IS_DATA(obj)          (G_TYPE_CHECK_INSTANCE_TYPE((obj), FB_TYPE_DATA))
+#define FB_IS_DATA_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), FB_TYPE_DATA))
+#define FB_DATA_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS((obj), FB_TYPE_DATA, FbDataClass))
+
+typedef struct _FbData FbData;
+typedef struct _FbDataClass FbDataClass;
+typedef struct _FbDataPrivate FbDataPrivate;
+
+struct _FbData
+{
+	GObject parent;
+	FbDataPrivate *priv;
+};
+
+struct _FbDataClass
+{
+	GObjectClass parent_class;
+};
+
+
+GType
+fb_data_get_type(void);
+
+FbData *
+fb_data_new(PurpleConnection *gc);
+
+gboolean
+fb_data_load(FbData *fata);
+
+void
+fb_data_save(FbData *fata);
+
+FbApi *
+fb_data_get_api(FbData *fata);
+
+gint
+fb_data_get_chatid(FbData *fata);
+
+PurpleConnection *
+fb_data_get_connection(FbData *fata);
+
+PurpleRoomlist *
+fb_data_get_roomlist(FbData *fata);
+
+void
+fb_data_set_roomlist(FbData *fata, PurpleRoomlist *list);
+
+#endif /* _FACEBOOK_DATA_H_ */
--- a/libpurple/protocols/facebook/facebook.c	Sat Jun 06 02:13:52 2015 -0400
+++ b/libpurple/protocols/facebook/facebook.c	Thu Jun 11 01:33:37 2015 -0400
@@ -30,135 +30,36 @@
 #include "version.h"
 
 #include "api.h"
+#include "data.h"
 #include "facebook.h"
 
-static const gchar *fb_props_strs[] = {
-	"cid",
-	"did",
-	"stoken",
-	"token"
-};
-
 static PurpleProtocol *my_protocol = NULL;
 
 static void
 fb_cb_api_error(FbApi *api, GError *error, gpointer data);
 
-static gboolean
-fb_props_load(PurpleConnection *gc)
-{
-	const gchar *str;
-	FbApi *api;
-	FbId id;
-	gboolean ret = TRUE;
-	guint i;
-	guint64 uint;
-	GValue val = G_VALUE_INIT;
-	PurpleAccount *acct;
-
-	acct = purple_connection_get_account(gc);
-	api = purple_connection_get_protocol_data(gc);
-	g_return_val_if_fail(FB_IS_API(api), FALSE);
-
-	for (i = 0; i < G_N_ELEMENTS(fb_props_strs); i++) {
-		str = purple_account_get_string(acct, fb_props_strs[i], NULL);
-
-		if (str == NULL) {
-			ret = FALSE;
-		}
-
-		g_value_init(&val, G_TYPE_STRING);
-		g_value_set_string(&val, str);
-		g_object_set_property(G_OBJECT(api), fb_props_strs[i], &val);
-		g_value_unset(&val);
-	}
-
-	str = purple_account_get_string(acct, "mid", NULL);
-
-	if (str != NULL) {
-		uint = g_ascii_strtoull(str, NULL, 10);
-		g_value_init(&val, G_TYPE_UINT64);
-		g_value_set_uint64(&val, uint);
-		g_object_set_property(G_OBJECT(api), "mid", &val);
-		g_value_unset(&val);
-	} else {
-		ret = FALSE;
-	}
-
-	str = purple_account_get_string(acct, "uid", NULL);
-
-	if (str != NULL) {
-		id = FB_ID_FROM_STR(str);
-		g_value_init(&val, FB_TYPE_ID);
-		g_value_set_int64(&val, id);
-		g_object_set_property(G_OBJECT(api), "uid", &val);
-		g_value_unset(&val);
-	} else {
-		ret = FALSE;
-	}
-
-	fb_api_rehash(api);
-	return ret;
-}
-
-static void
-fb_props_save(PurpleConnection *gc)
-{
-	const gchar *str;
-	FbApi *api;
-	gchar *dup;
-	guint i;
-	guint64 uint;
-	GValue val = G_VALUE_INIT;
-	PurpleAccount *acct;
-
-	acct = purple_connection_get_account(gc);
-	api = purple_connection_get_protocol_data(gc);
-	g_return_if_fail(FB_IS_API(api));
-
-	for (i = 0; i < G_N_ELEMENTS(fb_props_strs); i++) {
-		g_value_init(&val, G_TYPE_STRING);
-		g_object_get_property(G_OBJECT(api), fb_props_strs[i], &val);
-		str = g_value_get_string(&val);
-		purple_account_set_string(acct, fb_props_strs[i], str);
-		g_value_unset(&val);
-	}
-
-	g_value_init(&val, G_TYPE_UINT64);
-	g_object_get_property(G_OBJECT(api), "mid", &val);
-	uint = g_value_get_uint64(&val);
-	g_value_unset(&val);
-
-	dup = g_strdup_printf("%" G_GINT64_FORMAT, uint);
-	purple_account_set_string(acct, "mid", dup);
-	g_free(dup);
-
-	g_value_init(&val, G_TYPE_INT64);
-	g_object_get_property(G_OBJECT(api), "uid", &val);
-	uint = g_value_get_int64(&val);
-	g_value_unset(&val);
-
-	dup = g_strdup_printf("%" FB_ID_FORMAT, uint);
-	purple_account_set_string(acct, "uid", dup);
-	g_free(dup);
-}
-
 static void
 fb_cb_api_auth(FbApi *api, gpointer data)
 {
-	PurpleConnection *gc = data;
+	FbData *fata = data;
+	PurpleConnection *gc;
+
+	gc = fb_data_get_connection(fata);
 
 	purple_connection_update_progress(gc, _("Fetching contacts"), 2, 4);
-	fb_props_save(gc);
+	fb_data_save(fata);
 	fb_api_contacts(api);
 }
 
 static void
 fb_cb_api_connect(FbApi *api, gpointer data)
 {
-	PurpleConnection *gc = data;
+	FbData *fata = data;
+	PurpleConnection *gc;
 
-	fb_props_save(gc);
+	gc = fb_data_get_connection(fata);
+
+	fb_data_save(fata);
 	purple_connection_set_state(gc, PURPLE_CONNECTION_CONNECTED);
 }
 
@@ -208,15 +109,17 @@
 	const gchar *alias;
 	const gchar *csum;
 	FbApiUser *user;
+	FbData *fata = data;
 	FbId muid;
 	gchar uid[FB_ID_STRMAX];
 	GSList *l;
 	GValue val = G_VALUE_INIT;
 	PurpleAccount *acct;
 	PurpleBuddy *bdy;
-	PurpleConnection *gc = data;
+	PurpleConnection *gc;
 	PurpleGroup *grp;
 
+	gc = fb_data_get_connection(fata);
 	acct = purple_connection_get_account(gc);
 	grp = purple_blist_get_default_group();
 	alias = purple_account_get_private_alias(acct);
@@ -262,8 +165,10 @@
 static void
 fb_cb_api_error(FbApi *api, GError *error, gpointer data)
 {
-	PurpleConnection *gc = data;
+	FbData *fata = data;
+	PurpleConnection *gc;
 
+	gc = fb_data_get_connection(fata);
 	purple_connection_error(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR,
 	                        error->message);
 }
@@ -272,14 +177,39 @@
 fb_cb_api_message(FbApi *api, GSList *msgs, gpointer data)
 {
 	FbApiMessage *msg;
+	FbData *fata = data;
+	gchar tid[FB_ID_STRMAX];
 	gchar uid[FB_ID_STRMAX];
+	gint id;
 	GSList *l;
-	PurpleConnection *gc = data;
+	PurpleAccount *acct;
+	PurpleChatConversation *chat;
+	PurpleConnection *gc;
+
+	gc = fb_data_get_connection(fata);
+	acct = purple_connection_get_account(gc);
 
 	for (l = msgs; l != NULL; l = l->next) {
 		msg = l->data;
+		chat = NULL;
 		FB_ID_TO_STR(msg->uid, uid);
-		purple_serv_got_im(gc, uid, msg->text, PURPLE_MESSAGE_RECV, time(NULL));
+
+		if (msg->tid == 0) {
+			purple_serv_got_im(gc, uid, msg->text,
+		                           PURPLE_MESSAGE_RECV,
+			                   time(NULL));
+			continue;
+		}
+
+		FB_ID_TO_STR(msg->tid, tid);
+		chat = purple_conversations_find_chat_with_account(tid, acct);
+
+		if (chat != NULL) {
+			id = purple_chat_conversation_get_id(chat);
+			purple_serv_got_chat_in(gc, id, uid,
+			                        PURPLE_MESSAGE_RECV,
+		                                msg->text, time(NULL));
+		}
 	}
 }
 
@@ -288,14 +218,16 @@
 {
 	const gchar *statid;
 	FbApiPresence *pres;
+	FbData *fata = data;
 	gchar uid[FB_ID_STRMAX];
 	GSList *l;
 	PurpleAccount *acct;
 	PurpleBuddy *bdy;
-	PurpleConnection *gc = data;
+	PurpleConnection *gc;
 	PurplePresence *ppres;
 	PurpleStatusPrimitive pstat;
 
+	gc = fb_data_get_connection(fata);
 	acct = purple_connection_get_account(gc);
 
 	for (l = press; l != NULL; l = l->next) {
@@ -323,27 +255,96 @@
 static void
 fb_cb_api_thread_create(FbApi *api, FbId tid, gpointer data)
 {
-	purple_debug_info(NULL, "fb_cb_api_thread_create()");
+	FbData *fata = data;
+	gchar sid[FB_ID_STRMAX];
+	GHashTable *table;
+	PurpleConnection *gc;
+
+	gc = fb_data_get_connection(fata);
+	FB_ID_TO_STR(tid, sid);
+
+	table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
+	g_hash_table_insert(table, "name", g_strdup(sid));
+	purple_serv_join_chat(gc, table);
+	g_hash_table_destroy(table);
 }
 
 static void
 fb_cb_api_thread_info(FbApi *api, FbApiThread *thrd, gpointer data)
 {
-	purple_debug_info(NULL, "fb_cb_api_thread_info()");
+	FbApiUser *user;
+	FbData *fata = data;
+	gchar tid[FB_ID_STRMAX];
+	gchar uid[FB_ID_STRMAX];
+	gint id;
+	GSList *l;
+	PurpleChatConversation *chat;
+	PurpleConnection *gc;
+
+	gc = fb_data_get_connection(fata);
+	id = fb_data_get_chatid(fata);
+	FB_ID_TO_STR(thrd->tid, tid);
+
+	chat = purple_serv_got_joined_chat(gc, id, tid);
+	purple_chat_conversation_set_topic(chat, NULL, thrd->topic);
+
+	for (l = thrd->users; l != NULL; l = l->next) {
+		user = l->data;
+		FB_ID_TO_STR(user->uid, uid);
+		purple_chat_conversation_add_user(chat, uid, NULL, 0, FALSE);
+	}
 }
 
 static void
 fb_cb_api_thread_list(FbApi *api, GSList *thrds, gpointer data)
 {
-	purple_debug_info(NULL, "fb_cb_api_thread_list()");
+	FbApiUser *user;
+	FbData *fata = data;
+	gchar tid[FB_ID_STRMAX];
+	GSList *l;
+	GSList *m;
+	GString *gstr;
+	FbApiThread *thrd;
+	PurpleRoomlist *list;
+	PurpleRoomlistRoom *room;
+
+	list = fb_data_get_roomlist(fata);
+	gstr = g_string_new(NULL);
+
+	for (l = thrds; l != NULL; l = l->next) {
+		thrd = l->data;
+		FB_ID_TO_STR(thrd->tid, tid);
+		g_string_truncate(gstr, 0);
+
+		for (m = thrd->users; m != NULL; m = m->next) {
+			user = m->data;
+
+			if (gstr->len > 0) {
+				g_string_append(gstr, ", ");
+			}
+
+			g_string_append(gstr, user->name);
+		}
+
+		room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM,
+		                                tid, NULL);
+		purple_roomlist_room_add_field(list, room, thrd->topic);
+		purple_roomlist_room_add_field(list, room, gstr->str);
+		purple_roomlist_room_add(list, room);
+	}
+
+	purple_roomlist_set_in_progress(list, FALSE);
+	g_string_free(gstr, TRUE);
 }
 
 static void
 fb_cb_api_typing(FbApi *api, FbApiTyping *typg, gpointer data)
 {
+	FbData *fata = data;
 	gchar uid[FB_ID_STRMAX];
-	PurpleConnection *gc = data;
+	PurpleConnection *gc;
 
+	gc = fb_data_get_connection(fata);
 	FB_ID_TO_STR(typg->uid, uid);
 
 	if (typg->state) {
@@ -354,11 +355,70 @@
 }
 
 static void
+fb_blist_chat_create(GSList *buddies, gpointer data)
+{
+	const gchar *name;
+	FbApi *api;
+	FbData *fata = data;
+	FbId uid;
+	gpointer mptr;
+	GSList *l;
+	GSList *uids = NULL;
+	PurpleConnection *gc;
+	PurpleRequestCommonParameters *cpar;
+
+	gc = fb_data_get_connection(fata);
+	api = fb_data_get_api(fata);
+
+	if (g_slist_length(buddies) < 2) {
+		cpar = purple_request_cpar_from_connection(gc);
+		purple_notify_error(gc,
+		                    _("Initiate Chat"),
+		                    _("Failed to Initiate Chat"),
+		                    _("At least two initial chat participants"
+		                      " are required."),
+				    cpar);
+		return;
+	}
+
+	for (l = buddies; l != NULL; l = l->next) {
+		name = purple_buddy_get_name(l->data);
+		uid = FB_ID_FROM_STR(name);
+		mptr = g_memdup(&uid, sizeof uid);
+		uids = g_slist_prepend(uids, mptr);
+	}
+
+	fb_api_thread_create(api, uids);
+	g_slist_free_full(uids, g_free);
+}
+
+static void
+fb_blist_chat_init(PurpleBlistNode *node, gpointer data)
+{
+	FbData *fata = data;
+	GSList *select = NULL;
+	PurpleConnection *gc;
+
+	gc = fb_data_get_connection(fata);
+	select = g_slist_prepend(select, PURPLE_BUDDY(node));
+
+	fb_util_request_buddy(gc,
+	                      _("Initiate Chat"),
+	                      _("Initial Chat Participants"),
+	                      _("Select at least two initial participants."),
+	                      select, TRUE,
+			      G_CALLBACK(fb_blist_chat_create), NULL,
+			      fata);
+	g_slist_free(select);
+}
+
+static void
 fb_login(PurpleAccount *acct)
 {
 	const gchar *pass;
 	const gchar *user;
 	FbApi *api;
+	FbData *fata;
 	PurpleConnection *gc;
 
 	gc = purple_account_get_connection(acct);
@@ -371,51 +431,52 @@
 		return;
 	}
 
-	api = fb_api_new(gc);
-	purple_connection_set_protocol_data(gc, api);
+	fata = fb_data_new(gc);
+	api = fb_data_get_api(fata);
+	purple_connection_set_protocol_data(gc, fata);
 
 	g_signal_connect(api,
 	                 "auth",
 	                 G_CALLBACK(fb_cb_api_auth),
-	                 gc);
+	                 fata);
 	g_signal_connect(api,
 	                 "connect",
 	                 G_CALLBACK(fb_cb_api_connect),
-	                 gc);
+	                 fata);
 	g_signal_connect(api,
 	                 "contacts",
 	                 G_CALLBACK(fb_cb_api_contacts),
-	                 gc);
+	                 fata);
 	g_signal_connect(api,
 	                 "error",
 	                 G_CALLBACK(fb_cb_api_error),
-	                 gc);
+	                 fata);
 	g_signal_connect(api,
 	                 "message",
 	                 G_CALLBACK(fb_cb_api_message),
-	                 gc);
+	                 fata);
 	g_signal_connect(api,
 	                 "presence",
 	                 G_CALLBACK(fb_cb_api_presence),
-	                 gc);
+	                 fata);
 	g_signal_connect(api,
 	                 "thread-create",
 	                 G_CALLBACK(fb_cb_api_thread_create),
-	                 gc);
+	                 fata);
 	g_signal_connect(api,
 	                 "thread-info",
 	                 G_CALLBACK(fb_cb_api_thread_info),
-	                 gc);
+	                 fata);
 	g_signal_connect(api,
 	                 "thread-list",
 	                 G_CALLBACK(fb_cb_api_thread_list),
-	                 gc);
+	                 fata);
 	g_signal_connect(api,
 	                 "typing",
 	                 G_CALLBACK(fb_cb_api_typing),
-	                 gc);
+	                 fata);
 
-	if (!fb_props_load(gc)) {
+	if (!fb_data_load(fata)) {
 		user = purple_account_get_username(acct);
 		pass = purple_connection_get_password(gc);
 		purple_connection_update_progress(gc, _("Authenticating"),
@@ -432,12 +493,13 @@
 fb_close(PurpleConnection *gc)
 {
 	FbApi *api;
+	FbData *fata;
 
-	api = purple_connection_get_protocol_data(gc);
-	g_return_if_fail(FB_IS_API(api));
+	fata = purple_connection_get_protocol_data(gc);
+	api = fb_data_get_api(fata);
 
 	fb_api_disconnect(api);
-	g_object_unref(api);
+	g_object_unref(fata);
 	purple_connection_set_protocol_data(gc, NULL);
 }
 
@@ -464,15 +526,39 @@
 	return "facebook";
 }
 
+static GList *
+fb_client_blist_node_menu(PurpleBlistNode *node)
+{
+	FbData *fata;
+	GList *acts = NULL;
+	PurpleAccount *acct;
+	PurpleConnection *gc;
+	PurpleMenuAction *act;
+
+	acct = purple_buddy_get_account(PURPLE_BUDDY(node));
+	gc = purple_account_get_connection(acct);
+	fata = purple_connection_get_protocol_data(gc);
+
+	act = purple_menu_action_new(_("Initiate _Chat"),
+	                             PURPLE_CALLBACK(fb_blist_chat_init),
+	                             fata, NULL);
+	acts = g_list_prepend(acts, act);
+
+	return g_list_reverse(acts);
+}
+
 static gint
-fb_send(PurpleConnection *gc, PurpleMessage *msg)
+fb_im_send(PurpleConnection *gc, PurpleMessage *msg)
 {
 	const gchar *name;
 	const gchar *text;
 	FbApi *api;
+	FbData *fata;
 	FbId uid;
 
-	api = purple_connection_get_protocol_data(gc);
+	fata = purple_connection_get_protocol_data(gc);
+	api = fb_data_get_api(fata);
+
 	name = purple_message_get_recipient(msg);
 	uid = FB_ID_FROM_STR(name);
 
@@ -482,23 +568,184 @@
 }
 
 static guint
-fb_send_typing(PurpleConnection *gc, const gchar *name,
-               PurpleIMTypingState state)
+fb_im_send_typing(PurpleConnection *gc, const gchar *name,
+                  PurpleIMTypingState state)
 {
 	FbApi *api;
+	FbData *fata;
 	FbId uid;
 
-	api = purple_connection_get_protocol_data(gc);
+	fata = purple_connection_get_protocol_data(gc);
+	api = fb_data_get_api(fata);
 	uid = FB_ID_FROM_STR(name);
+
 	fb_api_typing(api, uid, state != PURPLE_IM_NOT_TYPING);
 	return 0;
 }
 
 static void
+fb_chat_join(PurpleConnection *gc, GHashTable *data)
+{
+	const gchar *name;
+	FbApi *api;
+	FbData *fata;
+	FbId tid;
+
+	name = g_hash_table_lookup(data, "name");
+	g_return_if_fail(name != NULL);
+
+	fata = purple_connection_get_protocol_data(gc);
+	api = fb_data_get_api(fata);
+	tid = FB_ID_FROM_STR(name);
+
+	fb_api_thread_info(api, tid);
+}
+
+static void
+fb_chat_invite(PurpleConnection *gc, gint id, const gchar *msg,
+               const gchar *who)
+{
+	const gchar *name;
+	FbApi *api;
+	FbData *fata;
+	FbId tid;
+	FbId uid;
+	PurpleChatConversation *chat;
+	PurpleRequestCommonParameters *cpar;
+
+	if (!FB_ID_IS_STR(who)) {
+		cpar = purple_request_cpar_from_connection(gc);
+		purple_notify_error(gc,
+		                    _("Invite Buddy Into Chat Room"),
+		                    _("Failed to Invite User"),
+		                    _("Invalid Facebook identifier."),
+				    cpar);
+		return;
+	}
+
+	fata = purple_connection_get_protocol_data(gc);
+	api = fb_data_get_api(fata);
+	chat = purple_conversations_find_chat(gc, id);
+
+	name = purple_conversation_get_name(PURPLE_CONVERSATION(chat));
+	tid = FB_ID_FROM_STR(name);
+	uid = FB_ID_FROM_STR(who);
+
+	purple_chat_conversation_add_user(chat, who, NULL, 0, TRUE);
+	fb_api_thread_invite(api, tid, uid);
+}
+
+static gint
+fb_chat_send(PurpleConnection *gc, gint id, PurpleMessage *msg)
+{
+	const gchar *name;
+	const gchar *text;
+	FbApi *api;
+	FbData *fata;
+	FbId tid;
+	PurpleAccount *acct;
+	PurpleChatConversation *chat;
+
+	acct = purple_connection_get_account(gc);
+	fata = purple_connection_get_protocol_data(gc);
+	api = fb_data_get_api(fata);
+	chat = purple_conversations_find_chat(gc, id);
+
+	name = purple_conversation_get_name(PURPLE_CONVERSATION(chat));
+	tid = FB_ID_FROM_STR(name);
+
+	text = purple_message_get_contents(msg);
+	fb_api_message(api, tid, TRUE, text);
+
+	name = purple_account_get_username(acct);
+	purple_serv_got_chat_in(gc, id, name,
+				purple_message_get_flags(msg),
+	                        purple_message_get_contents(msg),
+	                        time(NULL));
+	return 0;
+}
+
+static void
+fb_chat_set_topic(PurpleConnection *gc, gint id, const gchar *topic)
+{
+	const gchar *name;
+	FbApi *api;
+	FbData *fata;
+	FbId tid;
+	PurpleAccount *acct;
+	PurpleChatConversation *chat;
+
+	acct = purple_connection_get_account(gc);
+	fata = purple_connection_get_protocol_data(gc);
+	api = fb_data_get_api(fata);
+	chat = purple_conversations_find_chat(gc, id);
+
+	name = purple_conversation_get_name(PURPLE_CONVERSATION(chat));
+	tid = FB_ID_FROM_STR(name);
+
+	name = purple_account_get_username(acct);
+	purple_chat_conversation_set_topic(chat, name, topic);
+	fb_api_thread_topic(api, tid, topic);
+}
+
+static PurpleRoomlist *
+fb_roomlist_get_list(PurpleConnection *gc)
+{
+	FbApi *api;
+	FbData *fata;
+	GList *flds = NULL;
+	PurpleAccount *acct;
+	PurpleRoomlist *list;
+	PurpleRoomlistField *fld;
+
+	fata = purple_connection_get_protocol_data(gc);
+	api = fb_data_get_api(fata);
+	acct = purple_connection_get_account(gc);
+	list = purple_roomlist_new(acct);
+	fb_data_set_roomlist(fata, list);
+
+	fld = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING,
+	                                _("Topic"), "topic", FALSE);
+	flds = g_list_prepend(flds, fld);
+
+	fld = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING,
+	                                _("Users"), "users", FALSE);
+	flds = g_list_prepend(flds, fld);
+
+	flds = g_list_reverse(flds);
+	purple_roomlist_set_fields(list, flds);
+
+	fb_api_thread_list(api);
+	return list;
+}
+
+static void
+fb_roomlist_cancel(PurpleRoomlist *list)
+{
+	FbData *fata;
+	PurpleAccount *acct;
+	PurpleConnection *gc;
+	PurpleRoomlist *cist;
+
+	acct = purple_roomlist_get_account(list);
+	gc = purple_account_get_connection(acct);
+	fata = purple_connection_get_protocol_data(gc);
+	cist = fb_data_get_roomlist(fata);
+
+	if (G_LIKELY(cist == list)) {
+		fb_data_set_roomlist(fata, NULL);
+	}
+
+	purple_roomlist_set_in_progress(list, FALSE);
+	g_object_unref(list);
+}
+
+static void
 facebook_protocol_init(PurpleProtocol *protocol)
 {
-	protocol->id   = "prpl-facebook";
-	protocol->name = "Facebook";
+	protocol->id      = "prpl-facebook";
+	protocol->name    = "Facebook";
+	protocol->options = OPT_PROTO_CHAT_TOPIC;
 }
 
 static void
@@ -513,7 +760,7 @@
 static void
 facebook_protocol_client_iface_init(PurpleProtocolClientIface *iface)
 {
-
+	iface->blist_node_menu = fb_client_blist_node_menu;
 }
 
 static void
@@ -525,14 +772,17 @@
 static void
 facebook_protocol_im_iface_init(PurpleProtocolIMIface *iface)
 {
-	iface->send        = fb_send;
-	iface->send_typing = fb_send_typing;
+	iface->send        = fb_im_send;
+	iface->send_typing = fb_im_send_typing;
 }
 
 static void
 facebook_protocol_chat_iface_init(PurpleProtocolChatIface *iface)
 {
-
+	iface->join      = fb_chat_join;
+	iface->invite    = fb_chat_invite;
+	iface->send      = fb_chat_send;
+	iface->set_topic = fb_chat_set_topic;
 }
 
 static void
@@ -544,7 +794,8 @@
 static void
 facebook_protocol_roomlist_iface_init(PurpleProtocolRoomlistIface *iface)
 {
-
+	iface->get_list = fb_roomlist_get_list;
+	iface->cancel   = fb_roomlist_cancel;
 }
 
 PURPLE_DEFINE_TYPE_EXTENDED(
--- a/libpurple/protocols/facebook/id.h	Sat Jun 06 02:13:52 2015 -0400
+++ b/libpurple/protocols/facebook/id.h	Thu Jun 11 01:33:37 2015 -0400
@@ -25,6 +25,8 @@
 #include <glib.h>
 #include <glib/gprintf.h>
 
+#include "util.h"
+
 #define FB_ID_CONSTANT(v)  G_GINT64_CONSTANT(v)
 #define FB_ID_FORMAT       G_GINT64_FORMAT
 #define FB_ID_MODIFIER     G_GINT64_MODIFIER
@@ -36,6 +38,9 @@
 #define FB_ID_FROM_STR(s) \
 	g_ascii_strtoll(s, NULL, 10)
 
+#define FB_ID_IS_STR(s) \
+	fb_util_str_is(s, G_ASCII_DIGIT)
+
 #define FB_ID_TO_STR(i, s) \
 	g_sprintf(s, "%" FB_ID_FORMAT, (FbId) i)
 
--- a/libpurple/protocols/facebook/util.c	Sat Jun 06 02:13:52 2015 -0400
+++ b/libpurple/protocols/facebook/util.c	Thu Jun 11 01:33:37 2015 -0400
@@ -19,6 +19,8 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
+#include "internal.h"
+
 #include <string.h>
 #include <zlib.h>
 
@@ -55,6 +57,136 @@
 	return ret;
 }
 
+static void
+fb_util_request_buddy_ok(gpointer *mata, PurpleRequestFields *fields)
+{
+	FbUtilRequestBuddyFunc func = mata[0];
+	GList *l;
+	GList *select;
+	gpointer data = mata[2];
+	GSList *ret = NULL;
+	PurpleBuddy *bdy;
+	PurpleRequestField *field;
+
+	if (func == NULL) {
+		g_free(mata);
+		return;
+	}
+
+	field = purple_request_fields_get_field(fields, "buddy");
+	select = purple_request_field_list_get_selected(field);
+
+	for (l = select; l != NULL; l = l->next) {
+		bdy = purple_request_field_list_get_data(field, l->data);
+		ret = g_slist_prepend(ret, bdy);
+	}
+
+	ret = g_slist_reverse(ret);
+	func(ret, data);
+
+	g_slist_free(ret);
+	g_free(mata);
+}
+
+static void
+fb_util_request_buddy_cancel(gpointer *mata, PurpleRequestFields *fields)
+{
+	FbUtilRequestBuddyFunc func = mata[1];
+	gpointer data = mata[2];
+
+	if (func != NULL) {
+		func(NULL, data);
+	}
+
+	g_free(mata);
+}
+
+gpointer
+fb_util_request_buddy(PurpleConnection *gc, const gchar *title,
+                      const gchar *primary, const gchar *secondary,
+                      GSList *select, gboolean multi, GCallback ok_cb,
+		      GCallback cancel_cb, gpointer data)
+{
+	const gchar *alias;
+	const gchar *name;
+	gchar *str;
+	GList *items = NULL;
+	gpointer *mata;
+	GSList *buddies;
+	GSList *l;
+	PurpleAccount *acct;
+	PurpleRequestCommonParameters *cpar;
+	PurpleRequestField *field;
+	PurpleRequestFieldGroup *group;
+	PurpleRequestFields *fields;
+
+	mata = g_new0(gpointer, 3);
+	mata[0] = ok_cb;
+	mata[1] = cancel_cb;
+	mata[2] = data;
+
+	acct = purple_connection_get_account(gc);
+	buddies = purple_blist_find_buddies(acct, NULL);
+	buddies = g_slist_sort(buddies, (GCompareFunc) g_ascii_strcasecmp);
+
+	fields = purple_request_fields_new();
+	group = purple_request_field_group_new(NULL);
+	purple_request_fields_add_group(fields, group);
+
+	field = purple_request_field_list_new("buddy", NULL);
+	purple_request_field_list_set_multi_select(field, multi);
+	purple_request_field_set_required(field, TRUE);
+	purple_request_field_group_add_field(group, field);
+
+	for (l = buddies; l != NULL; l = l->next) {
+		name = purple_buddy_get_name(l->data);
+		alias = purple_buddy_get_alias(l->data);
+		str = g_strdup_printf("%s (%s)", alias, name);
+		purple_request_field_list_add_icon(field, str, NULL, l->data);
+		g_free(str);
+	}
+
+	for (l = select; l != NULL; l = l->next) {
+		name = purple_buddy_get_name(l->data);
+		alias = purple_buddy_get_alias(l->data);
+		str = g_strdup_printf("%s (%s)", alias, name);
+		items = g_list_append(items, str);
+	}
+
+	purple_request_field_list_set_selected(field, items);
+	g_slist_free(buddies);
+	g_list_free_full(items, g_free);
+
+	cpar = purple_request_cpar_from_connection(gc);
+	return purple_request_fields(gc, title, primary, secondary, fields,
+	                             _("Ok"),
+	                             G_CALLBACK(fb_util_request_buddy_ok),
+				     _("Cancel"),
+	                             G_CALLBACK(fb_util_request_buddy_cancel),
+				     cpar, mata);
+}
+
+gboolean
+fb_util_str_is(const gchar *str, GAsciiType type)
+{
+	gsize i;
+	gsize size;
+	guchar c;
+
+	g_return_val_if_fail(str != NULL, FALSE);
+	size = strlen(str);
+
+	for (i = 0; i < size; i++) {
+		c = (guchar) str[i];
+
+		if ((g_ascii_table[c] & type) == 0) {
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
 static voidpf
 fb_util_zalloc(voidpf opaque, uInt items, uInt size)
 {
--- a/libpurple/protocols/facebook/util.h	Sat Jun 06 02:13:52 2015 -0400
+++ b/libpurple/protocols/facebook/util.h	Thu Jun 11 01:33:37 2015 -0400
@@ -24,9 +24,22 @@
 
 #include <glib.h>
 
+#include "connection.h"
+
+typedef void (*FbUtilRequestBuddyFunc) (GSList *buddies, gpointer data);
+
 gchar *
 fb_util_randstr(gsize size);
 
+gpointer
+fb_util_request_buddy(PurpleConnection *gc, const gchar *title,
+                      const gchar *primary, const gchar *secondary,
+                      GSList *select, gboolean multi, GCallback ok_cb,
+                      GCallback cancel_cb, gpointer data);
+
+gboolean
+fb_util_str_is(const gchar *str, GAsciiType type);
+
 gboolean
 fb_util_zcompressed(const GByteArray *bytes);
 

mercurial