adium.1-3: 8b18fb33: pidgin-facebook-chat 1.6.0

evands at pidgin.im evands at pidgin.im
Tue Aug 18 12:36:19 EDT 2009


-----------------------------------------------------------------
Revision: 8b18fb3333bff7877236c43a6da78fa7a66362bc
Ancestor: 83dc5c1c57c15d70073f794b5d980374875c967d
Author: evands at pidgin.im
Date: 2009-08-18T16:32:28
Branch: im.pidgin.adium.1-3
URL: http://d.pidgin.im/viewmtn/revision/info/8b18fb3333bff7877236c43a6da78fa7a66362bc

Modified files:
        libpurple/protocols/facebook/facebook.nsi
        libpurple/protocols/facebook/fb_blist.c
        libpurple/protocols/facebook/fb_blist.h
        libpurple/protocols/facebook/fb_conversation.c
        libpurple/protocols/facebook/fb_info.c
        libpurple/protocols/facebook/fb_managefriends.c
        libpurple/protocols/facebook/fb_messages.c
        libpurple/protocols/facebook/fb_util.c
        libpurple/protocols/facebook/fb_util.h
        libpurple/protocols/facebook/libfacebook.c
        libpurple/protocols/facebook/libfacebook.h

ChangeLog: 

pidgin-facebook-chat 1.6.0

-------------- next part --------------
============================================================
--- libpurple/protocols/facebook/facebook.nsi	86d06564f9bcb28939ce31fea5cf6e899cb530a9
+++ libpurple/protocols/facebook/facebook.nsi	0ea82a12c41c433fc30f2431d1d6a0c9f53d1329
@@ -6,7 +6,7 @@ SetCompress off
 ; todo: SetBrandingImage
 ; HM NIS Edit Wizard helper defines
 !define PRODUCT_NAME "pidgin-facebookchat"
-!define PRODUCT_VERSION "1.53"
+!define PRODUCT_VERSION "1.60"
 !define PRODUCT_PUBLISHER "Eion Robb"
 !define PRODUCT_WEB_SITE "http://pidgin-facebookchat.googlecode.com/"
 !define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
============================================================
--- libpurple/protocols/facebook/fb_blist.c	a756fad89ceaabc66d0f1f4798a348b416396471
+++ libpurple/protocols/facebook/fb_blist.c	4bba8ded502f2a2210836c692c63af63ba0abcd0
@@ -21,16 +21,19 @@
 #include "libfacebook.h"
 #include "fb_connection.h"
 #include "fb_blist.h"
+#include "fb_util.h"
+#include "fb_friendlist.h"
+#include "blist.h"
 
-#include <json-glib/json-glib.h>
-
-static void set_buddies_offline(PurpleBuddy *buddy, GHashTable *online_buddies_list)
+static void set_buddies_offline(PurpleBuddy *buddy,
+		GHashTable *online_buddies_list)
 {
 	if (PURPLE_BUDDY_IS_ONLINE(buddy) &&
 		g_hash_table_lookup(online_buddies_list, buddy->name) == NULL)
 	{
 		purple_prpl_got_user_status(buddy->account, buddy->name,
-				purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE),
+				purple_primitive_get_id_from_type(
+					PURPLE_STATUS_OFFLINE),
 				NULL);
 	}
 }
@@ -51,39 +54,280 @@ static void buddy_icon_cb(FacebookAccoun
 
 	buddy = purple_find_buddy(fba->account, buddyname);
 	g_free(buddyname);
-	if (buddy == NULL)
-		return;
 
+	g_return_if_fail(buddy != NULL);
+
 	fbuddy = buddy->proto_data;
 
+	g_return_if_fail(fbuddy != NULL);
+
 	buddy_icon_data = g_memdup(data, data_len);
 
 	purple_buddy_icons_set_for_user(fba->account, buddy->name,
 			buddy_icon_data, data_len, fbuddy->thumb_url);
 }
 
-static void got_buddy_list_cb(FacebookAccount *fba, gchar *data,
-		gsize data_len, gpointer userdata)
+/**
+ * Find buddy names 
+ */
+static GList *get_buddies(FacebookAccount *fba, const gchar *uid,
+		const gchar *name, JsonArray *friend_list_ids)
 {
-	GSList *buddies_list;
-	GHashTable *online_buddies_list = g_hash_table_new(g_str_hash, g_str_equal);
-	PurpleBuddy *buddy;
+	GList *buddies;
+	GList *cur;
+
+	buddies = fb_get_buddies_friend_list(fba, uid, friend_list_ids);
+
+	// Initialize proto data for each buddy.
+	for (cur = buddies; cur != NULL; cur = cur->next)
+	{
+		PurpleBuddy *buddy;
+
+		buddy = (PurpleBuddy *) cur->data;
+
+		/* Set the FacebookBuddy structure */
+		if (buddy->proto_data == NULL)
+		{
+			FacebookBuddy *fbuddy;
+			gchar *buddy_icon_url;
+
+			fbuddy = g_new0(FacebookBuddy, 1);
+			fbuddy->buddy = buddy;
+			fbuddy->fba = fba;
+			fbuddy->uid = atoll(uid);
+			fbuddy->name = g_strdup(name);
+
+			// load the old buddy icon url from the icon 'checksum'
+			buddy_icon_url = (char *)
+				purple_buddy_icons_get_checksum_for_user(buddy);
+			if (buddy_icon_url != NULL)
+				fbuddy->thumb_url = g_strdup(buddy_icon_url);
+
+			buddy->proto_data = fbuddy;
+		}
+	}
+
+	return buddies;
+}
+
+static gboolean process_buddy_status(FacebookAccount *fba, PurpleBuddy *buddy,
+	JsonObject *userInfo)
+{
 	FacebookBuddy *fbuddy;
-	gchar *uid;
-	gchar *name;
-	gchar *status_text;
-	gchar *status_time_text;
+	gboolean status_changed;
+
+	status_changed = FALSE;
+	fbuddy = buddy->proto_data;
+
+	if (json_object_has_member(userInfo, "status"))
+	{
+		gchar *status_text;
+		const gchar *status_time_text;
+
+		status_time_text = json_node_get_string(
+			json_object_get_member(userInfo, "statusTimeRel"));
+		status_text = fb_strdup_withhtml(json_node_get_string(
+				json_object_get_member(userInfo, "status")));
+
+		/* set our last known status so that we don't re-set it */
+		if (!fba->last_status_message &&
+		    atoll(buddy->name) == fba->uid) {
+			fba->last_status_message = g_strdup(status_text);
+		}
+
+		if (strlen(status_time_text) == 0) {
+			status_time_text = NULL;
+		}
+
+		g_free(fbuddy->status_rel_time);
+		if (status_time_text != NULL) {
+			fbuddy->status_rel_time = 
+				fb_strdup_withhtml(status_time_text);
+		} else {
+			fbuddy->status_rel_time = NULL;
+		}
+
+		/* if the buddy status has changed, update the contact list */
+		if (fbuddy->status == NULL ||
+			!g_str_equal(fbuddy->status, status_text))
+		{
+			g_free(fbuddy->status);
+			fbuddy->status = g_strdup(status_text);
+			status_changed = TRUE;
+		}
+
+		g_free(status_text);
+	} else {
+		if (fbuddy->status != NULL) {
+			g_free(fbuddy->status);
+			fbuddy->status = NULL;
+			status_changed = TRUE;
+		}
+	}
+
+	return status_changed;
+}
+
+static void process_buddy_icon(FacebookAccount *fba, PurpleBuddy *buddy,
+	JsonObject *userInfo)
+{
+	FacebookBuddy *fbuddy;
 	gchar *buddy_icon_url;
+
+	fbuddy = buddy->proto_data;
+	
+	/* Set the buddy icon (if it hasn't changed) */
+	buddy_icon_url = json_node_dup_string(json_object_get_member(
+			userInfo, "thumbSrc"));
+	if (fbuddy->thumb_url == NULL ||
+	    !g_str_equal(fbuddy->thumb_url, buddy_icon_url))
+	{
+		g_free(fbuddy->thumb_url);
+		if (g_str_equal(buddy_icon_url,
+			"http://static.ak.fbcdn.net/pics/q_silhouette.gif"))
+		{
+			fbuddy->thumb_url = NULL;
+			/* User has no icon */
+			purple_buddy_icons_set_for_user(fba->account,
+				purple_buddy_get_name(buddy), NULL, 0, NULL);
+		}
+		else
+		{
+			gchar *search_tmp;
+
+			fbuddy->thumb_url = g_strdup(buddy_icon_url);
+
+			/* small icon at http://profile.ak.facebook.com/profile6/1845/74/q800753867_2878.jpg */
+			/* bigger icon at http://profile.ak.facebook.com/profile6/1845/74/n800753867_2878.jpg */
+			search_tmp = strstr(buddy_icon_url, "/q");
+			if (search_tmp)
+				*(search_tmp + 1) = 'n';
+				
+			/* Fetch their icon */
+			fb_post_or_get(fba, FB_METHOD_GET, "profile.ak.facebook.com",
+					buddy_icon_url, NULL,
+					buddy_icon_cb, g_strdup(purple_buddy_get_name(buddy)), FALSE);
+		}
+	}
+	g_free(buddy_icon_url);
+}
+
+static void process_buddies(FacebookAccount *fba, GHashTable *online_buddies_list,
+	JsonObject *nowAvailableList, gchar *uid, JsonObject *userInfo)
+{
+	const gchar *name;
 	gboolean idle;
-	guint32 error_number;
+	GList *buddies, *cur;
+	gboolean current_buddy_online;
 
-	gchar *search_tmp;
-	gchar *tmp;
+	JsonArray *friend_list_ids;
 
-	PurpleGroup *fb_group = NULL;
+	friend_list_ids = NULL;
+	name = json_node_get_string(json_object_get_member(userInfo, "name"));
 
-	gboolean current_buddy_online = FALSE;
+	/* look for "uid":{"i":_____} */
+	if (json_object_has_member(nowAvailableList, uid))
+	{
+		JsonObject *userBlistInfo;
+		userBlistInfo = json_node_get_object(
+			json_object_get_member(nowAvailableList, uid));
+		idle = json_node_get_boolean(
+			json_object_get_member(userBlistInfo, "i"));
+		if (json_object_has_member(userBlistInfo, "fl")) {
+			friend_list_ids = json_node_get_array(
+				json_object_get_member(userBlistInfo, "fl"));
+		}
 
+		current_buddy_online = TRUE;
+	} else {
+		/* if we're here, the buddy's info has been sent, 
+		 * but they're not actually online */
+		current_buddy_online = FALSE;
+		idle = FALSE;
+	}
+
+	/* is this us? */
+	if (atoll(uid) == fba->uid)
+	{
+		purple_connection_set_display_name(fba->pc, name);
+
+		/* check that we don't want to show ourselves */
+		current_buddy_online = !purple_account_get_bool(
+			fba->account, "facebook_hide_self", TRUE);
+	}
+
+	buddies = get_buddies(fba, uid, name, friend_list_ids);
+	for (cur = buddies; cur != NULL; cur = cur->next)
+	{
+		PurpleBuddy *buddy;
+		gboolean status_changed;
+
+		buddy = (PurpleBuddy *)cur->data;
+
+		process_buddy_icon(fba, buddy, userInfo);
+		status_changed = process_buddy_status(fba, buddy, userInfo);
+
+		purple_presence_set_idle(purple_buddy_get_presence(buddy),
+				idle, 0);
+
+		if (current_buddy_online)
+		{
+			/* Add buddy to the list of online buddies */
+			g_hash_table_insert(online_buddies_list, buddy->name, buddy);
+
+			// Set buddy as online in buddy list.  We check for several
+			// conditions before doing this, because if we set it always
+			// Pidgin has a bug where the logs go nuts with "x is online".
+			if (!PURPLE_BUDDY_IS_ONLINE(buddy) ||
+			    status_changed ||
+			    idle != purple_presence_is_idle(
+				purple_buddy_get_presence(buddy)))
+			{
+				purple_prpl_got_user_status(fba->account, buddy->name,
+					purple_primitive_get_id_from_type(
+						idle ? PURPLE_STATUS_AWAY :
+					       	PURPLE_STATUS_AVAILABLE), NULL);
+			}
+		}
+	}
+
+	/* update the blist if we have no previous alias */
+	fb_blist_set_alias(fba, uid, name);
+}
+
+static void process_notifications(FacebookAccount *fba,
+		JsonObject *notifications)
+{
+	if (notifications != NULL &&
+	    purple_account_get_check_mail(fba->account))
+	{
+		JsonNode *inboxCount_node = json_object_get_member(
+			notifications, "inboxCount");
+		if (inboxCount_node) {
+			gint inbox_count = json_node_get_int(inboxCount_node);
+			if (inbox_count &&
+			    inbox_count != fba->last_inbox_count) {
+				fba->last_inbox_count = inbox_count;
+				gchar *url = g_strdup("http://www.facebook.com/inbox/");
+				purple_notify_emails(
+					fba->pc, inbox_count,
+					FALSE, NULL, NULL,
+					(const char**) &(fba->account->username),
+					(const char**) &(url), NULL, NULL);
+				g_free(url);
+			}
+		}
+	}
+}
+
+static void got_buddy_list_cb(FacebookAccount *fba, gchar *data,
+		gsize data_len, gpointer userdata)
+{
+	GSList *buddies_list;
+	GHashTable *online_buddies_list = g_hash_table_new(
+			g_str_hash, g_str_equal);
+	gchar *uid;
+
 	purple_debug_info("facebook", "parsing buddy list\n");
 
 	if (fba == NULL)
@@ -96,60 +340,50 @@ static void got_buddy_list_cb(FacebookAc
 				_("Could not retrieve buddy list"));
 		return;
 	}
-	
+
 	purple_debug_misc("facebook", "buddy list\n%s\n", data);
-	
-	JsonNode *root;
-	root = json_parser_get_root(parser);
-	JsonObject *objnode;
-	objnode = json_node_get_object(root);
 
-	/* Check if the facebook group already exists (fixes #13) */
-	fb_group = purple_find_group("Facebook");
-
-	/* if logged out, this comes up */
-	/* for (;;);{"error":1357001,"errorSummary":"Not Logged In",
-		"errorDescription":"You must be logged in to do that.",
-		"payload":null,"bootload":[{"name":"js\/common.js.pkg.php",
-		"type":"js","src":"http:\/\/static.ak.fbcdn.net\/rsrc.php\/pkg\/59\
-		/98561\/js\/common.js.pkg.php"}]} */
-	if (json_object_has_member(objnode, "error"))
-	{
-		error_number = json_node_get_int(json_object_get_member(objnode, "error"));
-		if (error_number)
-		{
-			purple_connection_error_reason(fba->pc,
-					PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-					json_node_dup_string(json_object_get_member(objnode, "errorDescription")));
+	gchar *error = NULL;
+	JsonObject *objnode = fb_get_json_object(parser, &error);
+	if (error) {
+		purple_connection_error_reason(
+				fba->pc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				error);
 			g_object_unref(parser);
 			return;
-		}
 	}
-
+	
 	/* look for "userInfos":{ ... }, */
 	if (!json_object_has_member(objnode, "payload"))
 	{
 		g_object_unref(parser);
 		return;
 	}
-	objnode = json_node_get_object(json_object_get_member(objnode, "payload"));
+	objnode = json_node_get_object(json_object_get_member(
+			objnode, "payload"));
 	if (!json_object_has_member(objnode, "buddy_list"))
 	{
 		g_object_unref(parser);
 		return;
 	}
-	JsonObject *buddy_list = json_node_get_object(json_object_get_member(objnode, "buddy_list"));
+	JsonObject *buddy_list = json_node_get_object(json_object_get_member(
+			objnode, "buddy_list"));
 	if (!json_object_has_member(buddy_list, "userInfos"))
 	{
 		g_object_unref(parser);
 		return;
 	}
-	JsonObject *notifications = json_node_get_object(json_object_get_member(objnode, "notifications"));
-	
+
+	fb_process_friend_lists(fba, buddy_list);
+
+	// Iterate through the list of buddy infos sent to us.	
 	JsonObject *userInfos;
 	JsonObject *nowAvailableList;
-	userInfos = json_node_get_object(json_object_get_member(buddy_list, "userInfos"));
-	nowAvailableList = json_node_get_object(json_object_get_member(buddy_list, "nowAvailableList"));
+	userInfos = json_node_get_object(json_object_get_member(
+			buddy_list, "userInfos"));
+	nowAvailableList = json_node_get_object(json_object_get_member(
+			buddy_list, "nowAvailableList"));
 	GList *userIds;
 	userIds = json_object_get_members(userInfos);
 	GList *currentUserNode;
@@ -160,200 +394,34 @@ static void got_buddy_list_cb(FacebookAc
 		uid = currentUserNode->data;
 
 		JsonObject *userInfo;
-		userInfo = json_node_get_object(json_object_get_member(userInfos, uid));
-		name = json_node_dup_string(json_object_get_member(userInfo, "name"));
-
-		/* update the blist if we have no previous alias */
-		fb_blist_set_alias(fba, uid, name);
-
-		/* look for "uid":{"i":_____} */
-		if (json_object_has_member(nowAvailableList, uid))
-		{
-			JsonObject *userBlistInfo;
-			userBlistInfo = json_node_get_object(json_object_get_member(nowAvailableList, uid));
-			idle = json_node_get_boolean(json_object_get_member(userBlistInfo, "i"));
-			current_buddy_online = TRUE;
-		} else {
-			/* if we're here, the buddy's info has been sent, but they're not actually online */
-			current_buddy_online = FALSE;
-			idle = FALSE;
-		}
-
-		/* Set the buddy status text and time */
-		if (json_object_has_member(userInfo, "status"))
-		{
-			status_text = json_node_dup_string(json_object_get_member(userInfo, "status"));
-		} else {
-			status_text = NULL;
-		}
-
-		/* is this us? */
-		if (atoll(uid) == fba->uid)
-		{
-			purple_connection_set_display_name(fba->pc, name);
-
-			/* set our last known status so that we don't re-set it */
-			if (status_text && !fba->last_status_message)
-				fba->last_status_message = g_strdup(status_text);
-
-			/* check that we don't want to show ourselves */
-			if (purple_account_get_bool(fba->account, "facebook_hide_self", TRUE))
-			{
-				g_free(status_text);
-				g_free(name);
-				/* go on to the next buddy */
-				continue;
-			} else {
-				current_buddy_online = TRUE;
-			}
-		}
-
-		/* Is this a new buddy? */
-		buddy = purple_find_buddy(fba->account, uid);
-		if (buddy == NULL)
-		{
-			buddy = purple_buddy_new(fba->account, uid, NULL);
-			if (fb_group == NULL)
-			{
-				fb_group = purple_group_new("Facebook");
-				purple_blist_add_group(fb_group, NULL);
-			}
-			purple_blist_add_buddy(buddy, NULL, fb_group, NULL);
-		}
-		purple_presence_set_idle(purple_buddy_get_presence(buddy), idle, 0);
-
-		/* Set the FacebookBuddy structure */
-		if (buddy->proto_data == NULL)
-		{
-			fbuddy = g_new0(FacebookBuddy, 1);
-			fbuddy->buddy = buddy;
-			fbuddy->fba = fba;
-			fbuddy->uid = atoll(uid);
-			fbuddy->name = g_strdup(name);
-
-			/* load the old buddy icon url from the icon 'checksum' */
-			buddy_icon_url = (char *)purple_buddy_icons_get_checksum_for_user(buddy);
-			if (buddy_icon_url != NULL)
-				fbuddy->thumb_url = g_strdup(buddy_icon_url);
-
-			buddy->proto_data = fbuddy;
-		} else {
-			fbuddy = buddy->proto_data;
-		}
-
-		g_free(name);
-
-		if (status_text != NULL)
-		{
-			tmp = fb_strdup_withhtml(status_text);
-			g_free(status_text);
-			status_text = tmp;
-
-			status_time_text = json_node_dup_string(json_object_get_member(userInfo, "statusTimeRel"));
-			if (strlen(status_time_text) == 0)
-			{
-				g_free(status_time_text);
-				status_time_text = NULL;
-			}
-			g_free(fbuddy->status_rel_time);
-			if (status_time_text != NULL)
-			{
-				fbuddy->status_rel_time = fb_strdup_withhtml(status_time_text);
-				g_free(status_time_text);
-			} else {
-				fbuddy->status_rel_time = NULL;
-			}
-
-			/* if the buddy status has changed, update the contact list */
-			if (fbuddy->status == NULL || !g_str_equal(fbuddy->status, status_text))
-			{
-				tmp = fbuddy->status;
-				fbuddy->status = status_text;
-				g_free(tmp);
-				if (current_buddy_online)
-					purple_prpl_got_user_status(fba->account, buddy->name, purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE), NULL);
-			} else {
-				g_free(status_text);
-			}
-		} else {
-			if (fbuddy->status != NULL)
-			{
-				g_free(fbuddy->status);
-				fbuddy->status = NULL;
-				if (current_buddy_online)
-				{
-					/* update the status in the contact list */
-					purple_prpl_got_user_status(fba->account, buddy->name, purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE), NULL);
-				}
-			}
-		}
-
-		/* Set the buddy icon (if it hasn't changed) */
-		buddy_icon_url = json_node_dup_string(json_object_get_member(userInfo, "thumbSrc"));
-		if (fbuddy->thumb_url == NULL || !g_str_equal(fbuddy->thumb_url, buddy_icon_url))
-		{
-			g_free(fbuddy->thumb_url);
-			if (g_str_equal(buddy_icon_url, "http://static.ak.fbcdn.net/pics/q_silhouette.gif"))
-			{
-				fbuddy->thumb_url = NULL;
-				/* User has no icon */
-				purple_buddy_icons_set_for_user(fba->account,
-						purple_buddy_get_name(buddy), NULL, 0, NULL);
-			}
-			else
-			{
-				fbuddy->thumb_url = g_strdup(buddy_icon_url);
-
-				/* small icon at http://profile.ak.facebook.com/profile6/1845/74/q800753867_2878.jpg */
-				/* bigger icon at http://profile.ak.facebook.com/profile6/1845/74/n800753867_2878.jpg */
-				search_tmp = strstr(buddy_icon_url, "/q");
-				if (search_tmp)
-					*(search_tmp + 1) = 'n';
-				
-				/* Fetch their icon */
-				fb_post_or_get(fba, FB_METHOD_GET, "profile.ak.facebook.com",
-						buddy_icon_url + strlen("http://profile.ak.facebook.com"), NULL,
-						buddy_icon_cb, g_strdup(purple_buddy_get_name(buddy)), FALSE);
-			}
-		}
-		g_free(buddy_icon_url);
-
-		if (current_buddy_online)
-		{
-			/* Add buddy to the list of online buddies */
-			g_hash_table_insert(online_buddies_list, buddy->name, buddy);
-
-			/* Update the display of the buddy in the buddy list and make the user online */
-			if (!PURPLE_BUDDY_IS_ONLINE(buddy))
-				purple_prpl_got_user_status(fba->account, buddy->name, purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE), NULL);
-		}
+		userInfo = json_node_get_object(json_object_get_member(
+					userInfos, uid));
+		// Process the user, which generally consists of updating
+		// state info such as name, idle item, status message,etc.
+		process_buddies(fba, online_buddies_list, nowAvailableList,
+				uid, userInfo);
 	}
 	g_list_free(userIds);
 
+	// Set users offline.  We do this in a seperate function because FB
+	// only sends us a list of users who are online.  We find the users
+	// that are not in the union of of buddy list users + online, and
+	// mark them as offline.
 	buddies_list = purple_find_buddies(fba->account, NULL);
 	if (buddies_list != NULL)
 	{
-		g_slist_foreach(buddies_list, (GFunc)set_buddies_offline, online_buddies_list);
+		g_slist_foreach(
+			buddies_list,
+			(GFunc)set_buddies_offline, online_buddies_list);
 		g_slist_free(buddies_list);
 	}
 	g_hash_table_destroy(online_buddies_list);
 	
-	if (notifications != NULL && purple_account_get_check_mail(fba->account))
-	{
-		JsonNode *inboxCount_node = json_object_get_member(notifications, "inboxCount");
-		if (inboxCount_node)
-		{
-			gint inbox_count = json_node_get_int(inboxCount_node);
-			if (inbox_count && inbox_count != fba->last_inbox_count)
-			{
-				fba->last_inbox_count = inbox_count;
-				gchar *url = g_strdup("http://www.facebook.com/inbox/");
-				purple_notify_emails(fba->pc, inbox_count, FALSE, NULL, NULL, (const char**) &(fba->account->username), (const char**) &(url), NULL, NULL);
-				g_free(url);
-			}
-		}
-	}
-	
+	// The buddy list also contains notifications data.  Process and
+	// display is appropriate.
+	process_notifications(fba, json_node_get_object(
+		json_object_get_member(objnode, "notifications")));
+
 	g_object_unref(parser);
 }
 
@@ -426,3 +494,24 @@ void fb_blist_set_alias(FacebookAccount 
 	/* In case user removes an alias, we have the server as fallback */
 	serv_got_alias(fba->pc, id, name);
 }
+
+void fb_blist_init(FacebookAccount *fba)
+{
+	fb_friendlist_init(fba);
+
+	fb_get_buddy_list(fba);
+
+	/* periodically check for updates to your buddy list */
+	fba->buddy_list_timer = purple_timeout_add_seconds(60,
+			fb_get_buddy_list, fba);
+
+}
+
+void fb_blist_destroy(FacebookAccount *fba)
+{
+	if (fba->buddy_list_timer) {
+		purple_timeout_remove(fba->buddy_list_timer);
+	}
+
+	fb_friendlist_destroy(fba);
+}
============================================================
--- libpurple/protocols/facebook/fb_blist.h	282d636d6e7f6011bec4f39d5cc9483b1be8b199
+++ libpurple/protocols/facebook/fb_blist.h	e77ea2b71a92a022052a1083631d810b540385b4
@@ -29,4 +29,7 @@ void fb_blist_set_alias(FacebookAccount 
 void fb_blist_set_alias(FacebookAccount *fba, const char *id,
 		const char *name);
 
+void fb_blist_init(FacebookAccount *fba);
+void fb_blist_destroy(FacebookAccount *fba);
+
 #endif /* FACEBOOK_BLIST_H */
============================================================
--- libpurple/protocols/facebook/fb_conversation.c	259358150129a9c835a26193282d46f2dd284d18
+++ libpurple/protocols/facebook/fb_conversation.c	c7a209ce60af9d7660eeceecb1941275fe47bbe1
@@ -49,7 +49,7 @@ void fb_conversation_handle_message(Face
 	if (fba->uid != atoll(from) || fba->uid == atoll(to)) {
 		purple_debug_info("facebook",
 			"displaying received message %lld: %s\n",
-			message_time, message_text);
+			(long long int) message_time, message_text);
 		// TODO/FIXME: cheat here by changing formatting colors.
 		// Or add an option to just disable history on conv open.  TBD.
 		serv_got_im(fba->pc, from, message_text,
@@ -68,7 +68,7 @@ void fb_conversation_handle_message(Face
 	{
 		purple_debug_info("facebook",
 			"displaying sent message %lld: %s\n",
-			message_time, message_text);
+			(long long int) message_time, message_text);
 
 		serv_got_im(fba->pc, to, message_text,
 			log? 
@@ -96,7 +96,6 @@ static void fb_history_fetch_cb(Facebook
 	gsize data_len, gpointer userdata)
 {
 	JsonParser *parser;
-	JsonNode *root;
 	JsonObject *object, *payload;
 	JsonArray *history;
 	guint i;
@@ -114,10 +113,9 @@ static void fb_history_fetch_cb(Facebook
 	min_time = atoll((char *) userdata);
 	g_free(userdata);
 	purple_debug_info("facebook", "history fetch with min time of %lld\n",
-		       min_time);	
+		       (long long int) min_time);	
 
-	root = json_parser_get_root(parser);
-	object = json_node_get_object(root);
+	object = fb_get_json_object(parser, NULL);
 	payload = json_node_get_object(
 		json_object_get_member(object, "payload"));
 	history = json_node_get_array(
@@ -159,7 +157,7 @@ static void fb_history_fetch_cb(Facebook
 			if (message_time > min_time) {
 				purple_debug_info("facebook",
 					"displaying history message %lld\n",
-					message_time);
+					(long long int) message_time);
 				fb_conversation_handle_message(
 					fba, from, to, message_time, message,
 					min_time != 0);
@@ -188,7 +186,7 @@ void fb_history_fetch(FacebookAccount *f
 	gchar *url = g_strdup_printf("/ajax/chat/history.php?id=%s", who);
 	fb_post_or_get(
 		fba, FB_METHOD_GET, NULL, url, NULL, fb_history_fetch_cb,
-		g_strdup_printf("%lld", min_time), FALSE);
+		g_strdup_printf("%lld", (long long int) min_time), FALSE);
 	g_free(url);
 }
 
@@ -224,7 +222,10 @@ static void fb_conversation_created(Purp
 	purple_debug_info("facebook", "conversation created with %s\n",
 		conv->name);
 
-	fb_history_fetch(account->gc->proto_data, conv->name, TRUE);
+	if (purple_account_get_bool(account, "facebook_show_history", TRUE))
+	{
+		fb_history_fetch(account->gc->proto_data, conv->name, TRUE);
+	}
 }
 
 gboolean fb_conversation_is_fb(PurpleConversation *conv)
============================================================
--- libpurple/protocols/facebook/fb_info.c	3eb9ceaf6170bc0b2ab8f2ef73d9adec28363839
+++ libpurple/protocols/facebook/fb_info.c	b41f1bf46271eaa87317bdaa410b13abde797e7a
@@ -20,6 +20,7 @@
 
 #include "fb_connection.h"
 #include "fb_info.h"
+#include "fb_blist.h"
 
 /*
  * TODO: Do we really want to do this?  Maybe we could just set a
@@ -130,7 +131,7 @@ static void fb_get_info_cb(FacebookAccou
 		value_tmp2 = g_strndup(value_tmp, strstr(value_tmp, "</title>")-value_tmp);
 		value_tmp = g_strchomp(purple_markup_strip_html(value_tmp2));
 		purple_notify_user_info_add_pair(user_info, _("Name"), value_tmp);
-		serv_got_alias(fba->pc, uid, value_tmp);
+		fb_blist_set_alias(fba, uid, value_tmp);
 		g_free(value_tmp);
 		g_free(value_tmp2);
 	}
============================================================
--- libpurple/protocols/facebook/fb_managefriends.c	48d9104fae04b40e366b570f78e4b366e0d87a43
+++ libpurple/protocols/facebook/fb_managefriends.c	1d9f2f9e32feef9aa049321c952ab8cb205f1162
@@ -87,6 +87,9 @@ static void fb_check_friend_request_cb(F
 	FacebookBuddy *buddy;
 	gchar *search_start = data;
 
+	g_return_if_fail(data_len > 0);
+	g_return_if_fail(data != NULL);
+
 	/* loop through the data and look for confirm_friend_add_([0-9]*)" */
 	while ((search_start = strstr(search_start, uid_pre_text)))
 	{
@@ -169,10 +172,14 @@ void fb_add_buddy(PurpleConnection *pc, 
 
 	if (!purple_account_get_bool(
 				fba->account, "facebook_manage_friends", FALSE)) {
+		/*
+		 * We used to pop up dialogs here but if a user renamed a group,
+		 * this would spawn message for each person in the buddy list.  Bad!
 		purple_notify_info(fba->pc, _("Friend not added"),
 				_("Adding Facebook friends via Pidgin is disabled"),
 				_("Either add a friend via Facebook.com or edit your account preferences"));
-		// TODO: Message here
+		*/
+		purple_debug_warning("facebook", "attempted to add %s but was blocked\n", buddy->name);
 		return;
 	}
 
@@ -197,24 +204,3 @@ void fb_add_buddy(PurpleConnection *pc, 
 	g_free(url);
 }
 
-#if 0
-/* This code should never be reinstated in it's current form.  Period.  See
- * issue 185 for why */
-static void fb_remove_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group)
-{
-	gchar *postdata;
-	FacebookAccount *fba = pc->proto_data;
-
-	if (atoll(buddy->name) == fba->uid)
-	{
-		purple_account_set_bool(fba->account, "facebook_hide_self", TRUE);
-		return;
-	}
-
-	postdata = g_strdup_printf("uid=%s&post_form_id=%s", buddy->name, fba->post_form_id);
-
-	fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/removefriend.php", postdata, NULL, NULL, FALSE);
-
-	g_free(postdata);
-}
-#endif
============================================================
--- libpurple/protocols/facebook/fb_messages.c	55cba18f6f06fcf967d93af64fce1cbb06224521
+++ libpurple/protocols/facebook/fb_messages.c	09b6b82775e77886d5aff74654063c9f70e7e7fb
@@ -22,9 +22,8 @@
 #include "fb_connection.h"
 #include "fb_conversation.h"
 #include "fb_blist.h"
+#include "fb_util.h"
 
-#include <json-glib/json-glib.h>
-
 #include "conversation.h"
 
 typedef struct _FacebookOutgoingMessage FacebookOutgoingMessage;
@@ -207,18 +206,14 @@ static void got_new_messages(FacebookAcc
 		return;
 	}
 
-	JsonNode *root;
-	root = json_parser_get_root(parser);
+	JsonObject *objnode = fb_get_json_object(parser, NULL);
 
-	JsonObject *objnode;
-	objnode = json_node_get_object(root);
-
 	if (json_object_has_member(objnode, "t")) {
 		const gchar* command = json_node_get_string(json_object_get_member(objnode, "t"));
 		if (g_str_equal(command, "refresh")) {
-			int seq = json_node_get_int(json_object_get_member(objnode, "seq"));
-			if (seq) {
-				fba->message_fetch_sequence = seq;	
+			if (json_object_has_member(objnode, "seq")) {
+				fba->message_fetch_sequence = json_node_get_int(
+					json_object_get_member(objnode, "seq"));
 			}
 
 			/* grab history items for all open conversations */
@@ -297,7 +292,7 @@ static gboolean fb_get_new_messages(Face
 
 	fetch_server = g_strdup_printf("%d.channel%s.facebook.com", 0, channel_number);
 	/* use the current time in the url to get past any transparent proxy caches */
-	fetch_url = g_strdup_printf("/x/%lu/%s/p_%" G_GINT64_FORMAT "=%d", time(NULL), (fba->is_idle?"false":"true"), fba->uid, fba->message_fetch_sequence);
+	fetch_url = g_strdup_printf("/x/%lu/%s/p_%" G_GINT64_FORMAT "=%d", (gulong)time(NULL), (fba->is_idle?"false":"true"), fba->uid, fba->message_fetch_sequence);
 
 	fb_post_or_get(fba, FB_METHOD_GET, fetch_server, fetch_url, NULL, got_new_messages, fba->pc, TRUE);
 	fba->last_messages_download_time = now;
@@ -311,29 +306,12 @@ static void fb_send_im_cb(FacebookAccoun
 static void fb_send_im_cb(FacebookAccount *fba, gchar *data, gsize data_len, gpointer user_data)
 {
 	FacebookOutgoingMessage *msg = user_data;
-	gint error_number;
-	const gchar *error_summary;
 	JsonParser *parser;
-	JsonNode *root;
 	JsonObject *object;
 	PurpleConversation *conv;
+	gchar *error = NULL;
 
-	/* NULL data crashes on Windows */
-	if (data == NULL)
-	{
-		data = "(null)";
-	}
-	
 	purple_debug_misc("facebook", "sent im response: %s\n", data);
-	/* for (;;);{"error":1356003,"errorSummary":"Send destination not online",
-		"errorDescription":"This person is no longer online.","payload":null,
-		"bootload":[{"name":"js\/common.js.pkg.php","type":"js",
-		"src":"http:\/\/static.ak.fbcdn.net\/rsrc.php\/pkg\/59\/98936\
-		/js\/common.js.pkg.php"}]} */
-	/* for (;;);{"error":0,"errorSummary":"","errorDescription":"No error.",
-		"payload":[],"bootload":[{"name":"js\/common.js.pkg.php","type":"js",
-		"src":"http:\/\/static.ak.fbcdn.net\/rsrc.php\/pkg\/59\/98936\
-		/js\/common.js.pkg.php"}]} */
 	
 	parser = fb_get_parser(data, data_len);
 	if (!parser) {
@@ -341,15 +319,11 @@ static void fb_send_im_cb(FacebookAccoun
 		purple_debug_warning("facebook", "bad data while parsing sent IM\n");
 		return;
 	}
-	root = json_parser_get_root(parser);
-	object = json_node_get_object(root);
+	object = fb_get_json_object(parser, &error);
 	
-	error_number = json_node_get_int(json_object_get_member(object, "error"));
-	error_summary = json_node_get_string(json_object_get_member(object, "errorSummary"));
-	
-	if (error_number)
+	if (error)
 	{
-		purple_debug_error("facebook", "sent im error: %s\n", error_summary);
+		purple_debug_error("facebook", "sent im error: %s\n", error);
 		/* there was an error, either report it or retry */
 		if (msg->retry_count++ < FB_MAX_MSG_RETRY)
 		{
@@ -362,7 +336,7 @@ static void fb_send_im_cb(FacebookAccoun
 		{
 			conv = purple_conversation_new(PURPLE_CONV_TYPE_IM,
 					fba->account, msg->who);
-			purple_conversation_write(conv, NULL, error_summary,
+			purple_conversation_write(conv, NULL, error,
 					PURPLE_MESSAGE_ERROR, msg->time);
 		}
 		
@@ -381,7 +355,7 @@ static gboolean fb_send_im_fom(FacebookO
 	encoded_message = g_strdup(purple_url_encode(msg->message));
 	postdata = g_strdup_printf("msg_text=%s&msg_id=%d&to=%s&client_time=%lu&post_form_id=%s",
 			encoded_message, msg->msg_id, msg->who,
-			msg->time,
+			(gulong) msg->time,
 			msg->fba->post_form_id ? msg->fba->post_form_id : "0");
 	g_free(encoded_message);
 
@@ -432,7 +406,7 @@ void got_reconnect_json(FacebookAccount 
 	gchar *new_channel_number;
 	
 	JsonParser *parser;
-	JsonNode *root;
+	JsonObject *objnode;
 
 	parser = fb_get_parser(data, data_len);
 
@@ -445,9 +419,7 @@ void got_reconnect_json(FacebookAccount 
 		return;
 	}
 
-	root = json_parser_get_root(parser);
-	JsonObject *objnode;
-	objnode = json_node_get_object(root);
+	objnode = fb_get_json_object(parser, NULL);
 
 	JsonObject *payload = json_node_get_object(json_object_get_member(objnode, "payload"));
 	
============================================================
--- libpurple/protocols/facebook/fb_util.c	23606c4fef64d4b023b7dca41ac62a8b499a4924
+++ libpurple/protocols/facebook/fb_util.c	094060765df4b0671d66097a7ad6bfc0e1e87650
@@ -24,9 +24,179 @@
  * UTILITY CODE                                                              *
  *****************************************************************************/
 
+gchar *fb_convert_unicode(const gchar *input)
+{
+	/* \u00e9t\u00e9 should be ?t? */
+
+	gunichar unicode_char;
+	gchar unicode_char_str[6];
+	gint unicode_char_len;
+	gchar *next_pos;
+	gchar *input_string;
+	gchar *output_string;
+
+	if (input == NULL)
+		return NULL;
+
+	next_pos = input_string = g_strdup(input);
+
+	/* purple_debug_info("facebook", "unicode convert: in: %s\n", input); */
+	while ((next_pos = strstr(next_pos, "\\u")))
+	{
+		/* grab the unicode */
+		sscanf(next_pos, "\\u%4x", &unicode_char);
+		/* turn it to a char* */
+		unicode_char_len = g_unichar_to_utf8(unicode_char, unicode_char_str);
+		/* shove it back into the string */
+		g_memmove(next_pos, unicode_char_str, unicode_char_len);
+		/* move all the data after the \u0000 along */
+		g_stpcpy(next_pos + unicode_char_len, next_pos + 6);
+	}
+
+	/* purple_debug_info("facebook", "unicode convert: out: %s\n", input); */
+	output_string = g_strcompress(input_string);
+	g_free(input_string);
+
+	return output_string;
+}
+
+/* Like purple_strdup_withhtml, but escapes htmlentities too */
+gchar *fb_strdup_withhtml(const gchar *src)
+{
+	gulong destsize, i, j;
+	gchar *dest;
+
+	g_return_val_if_fail(src != NULL, NULL);
+
+	/* New length is (length of src) + (number of \n's * 3) + (number of &'s * 5) +
+		(number of <'s * 4) + (number of >'s *4) + (number of "'s * 6) -
+		(number of \r's) + 1 */
+	destsize = 1;
+	for (i = 0; src[i] != '\0'; i++)
+	{
+		if (src[i] == '\n' || src[i] == '<' || src[i] == '>')
+			destsize += 4;
+		else if (src[i] == '&')
+			destsize += 5;
+		else if (src[i] == '"')
+			destsize += 6;
+		else if (src[i] != '\r')
+			destsize++;
+	}
+
+	dest = g_malloc(destsize);
+
+	/* Copy stuff, ignoring \r's, because they are dumb */
+	for (i = 0, j = 0; src[i] != '\0'; i++) {
+		if (src[i] == '\n') {
+			strcpy(&dest[j], "<BR>");
+			j += 4;
+		} else if (src[i] == '<') {
+			strcpy(&dest[j], "&lt;");
+			j += 4;
+		} else if (src[i] == '>') {
+			strcpy(&dest[j], "&gt;");
+			j += 4;
+		} else if (src[i] == '&') {
+			strcpy(&dest[j], "&amp;");
+			j += 5;
+		} else if (src[i] == '"') {
+			strcpy(&dest[j], "&quot;");
+			j += 6;
+		} else if (src[i] != '\r')
+			dest[j++] = src[i];
+	}
+
+	dest[destsize-1] = '\0';
+
+	return dest;
+}
+
+gint64 fb_time_kludge(gint initial_time)
+{
+	if (sizeof(gint) >= sizeof(gint64))
+		return initial_time;
+	
+	gint64 now_millis = (gint64) time(NULL);
+	now_millis *= 1000;
+	now_millis &= 0xFFFFFFFF00000000LL;
+	gint64 final_time = now_millis | ((guint)initial_time);
+
+	return final_time;
+}
+
+JsonParser *fb_get_parser(const gchar *data, gsize data_len)
+{
+	JsonParser *parser;
+
+	if (data == NULL) {
+		return NULL;
+	}
+
+	data = g_strstr_len(data, data_len, "for (;;);");
+	if (!data) {
+		return NULL;
+	} else {
+		data += strlen("for (;;);");
+	}
+
+	parser = json_parser_new();
+	if (!json_parser_load_from_data(parser, data, -1, NULL)) {
+		g_object_unref(parser);
+		return NULL;
+	}
+
+	return parser;
+}
+
+JsonObject *fb_get_json_object(JsonParser *parser, char **error_message)
+{
+	JsonNode *root;
+	root = json_parser_get_root(parser);
+	JsonObject *objnode;
+	objnode = json_node_get_object(root);
+
+	/* Sample error messages */
+	/* for (;;);{"error":1357001,"errorSummary":"Not Logged In",
+		"errorDescription":"You must be logged in to do that.",
+		"payload":null,"bootload":[{"name":"js\/common.js.pkg.php",
+		"type":"js","src":"http:\/\/static.ak.fbcdn.net\/rsrc.php\/pkg\/59\
+		/98561\/js\/common.js.pkg.php"}]} */
+	if (json_object_has_member(objnode, "error"))
+	{
+		guint32 error_number;
+		const char *summary;
+		const char *description;
+
+		error_number = json_node_get_int(
+			json_object_get_member(objnode, "error"));
+		summary = json_node_get_string(
+			json_object_get_member(objnode, "errorSummary"));
+		description = json_node_get_string(
+			json_object_get_member(objnode, "errorDescription"));
+
+		if (error_number)
+		{
+			purple_debug_error("facebook",
+				"got error from facebook of %s (%s)",
+				summary, description);
+			// Pass error message to calling function if they asked for it.
+			if (error_message) {
+				*error_message = g_strdup(description);
+			}
+		}
+	}
+
+	return objnode;
+}
+
 /* Converts *text* into <b>text</b>  and _text_ into <i>text</i> */
 gchar *fb_replace_styled_text(const gchar *text)
 {
+#ifdef __ARM_EABI__
+	return g_strdup(text);
+#else /*__ARM_EABI__*/
+#if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 14
 	if (glib_check_version(2, 14, 0))
 	{
 		return g_strdup(text);
@@ -52,13 +222,27 @@ gchar *fb_replace_styled_text(const gcha
 		
 		dup_text = g_strdup(text);
 		midway_string = g_regex_replace(underline_regex, dup_text,
-			-1, 0, "<u>\\1</u>", 0, NULL);
+			strlen(dup_text), 0, "<u>\\1</u>", 0, NULL);
+		if (midway_string == NULL)
+		{
+			purple_debug_warning("facebook", "regex failed for underline\n");
+			return dup_text;
+		}
 		g_free(dup_text);
 		output_string = g_regex_replace(bold_regex, midway_string,
-			-1, 0, "\\1<b>\\2</b>", 0, NULL);
+			strlen(midway_string), 0, "\\1<b>\\2</b>", 0, NULL);
+		if (output_string == NULL)
+		{
+			purple_debug_warning("facebook", "regex failed for bold\n");
+			return midway_string;
+		}
 		g_free(midway_string);
 		
 		return output_string;
 	}
+#else /* GLIB check */
+	return g_strdup(text);
+#endif /* GLIB check */
+#endif /*__ARM_EABI__*/
 }
 
============================================================
--- libpurple/protocols/facebook/fb_util.h	33093b85583658b53d9519de66ab7da32ebd5076
+++ libpurple/protocols/facebook/fb_util.h	53baf5d5d5678ff04dc221e8d629b56d3f3a67f5
@@ -22,8 +22,15 @@
 #define FACEBOOK_UTIL_H
 
 #include "libfacebook.h"
+#include <json-glib/json-glib.h>
 
+JsonParser *fb_get_parser(const gchar *data, gsize data_len);
+JsonObject *fb_get_json_object(JsonParser *parser, char **error_message);
+
 gchar *fb_replace_styled_text(const gchar *text);
+gchar *fb_strdup_withhtml(const gchar *src);
+gchar *fb_convert_unicode(const gchar *input);
+gint64 fb_time_kludge(int initial_time);
 
 #endif /* FACEBOOK_UTIL_H */
 
============================================================
--- libpurple/protocols/facebook/libfacebook.c	4a5dcafa34bf51425459fa4d44852b720bbc3661
+++ libpurple/protocols/facebook/libfacebook.c	83c57c5d42dc61b5fafa9b38bd25fd3e2773b20c
@@ -27,136 +27,12 @@
 #include "fb_messages.h"
 #include "fb_notifications.h"
 #include "fb_search.h"
+#include "fb_friendlist.h"
 
-/******************************************************************************/
-/* Utility functions */
-/******************************************************************************/
+static void fb_login_cb(FacebookAccount *fba, gchar *response, gsize len,
+		gpointer userdata);
+static void fb_close(PurpleConnection *pc);
 
-gchar *fb_convert_unicode(const gchar *input)
-{
-	/* \u00e9t\u00e9 should be ?t? */
-
-	gunichar unicode_char;
-	gchar unicode_char_str[6];
-	gint unicode_char_len;
-	gchar *next_pos;
-	gchar *input_string;
-	gchar *output_string;
-
-	if (input == NULL)
-		return NULL;
-
-	next_pos = input_string = g_strdup(input);
-
-	/* purple_debug_info("facebook", "unicode convert: in: %s\n", input); */
-	while ((next_pos = strstr(next_pos, "\\u")))
-	{
-		/* grab the unicode */
-		sscanf(next_pos, "\\u%4x", &unicode_char);
-		/* turn it to a char* */
-		unicode_char_len = g_unichar_to_utf8(unicode_char, unicode_char_str);
-		/* shove it back into the string */
-		g_memmove(next_pos, unicode_char_str, unicode_char_len);
-		/* move all the data after the \u0000 along */
-		g_stpcpy(next_pos + unicode_char_len, next_pos + 6);
-	}
-
-	/* purple_debug_info("facebook", "unicode convert: out: %s\n", input); */
-	output_string = g_strcompress(input_string);
-	g_free(input_string);
-
-	return output_string;
-}
-
-/* Like purple_strdup_withhtml, but escapes htmlentities too */
-gchar *fb_strdup_withhtml(const gchar *src)
-{
-	gulong destsize, i, j;
-	gchar *dest;
-
-	g_return_val_if_fail(src != NULL, NULL);
-
-	/* New length is (length of src) + (number of \n's * 3) + (number of &'s * 5) +
-		(number of <'s * 4) + (number of >'s *4) + (number of "'s * 6) -
-		(number of \r's) + 1 */
-	destsize = 1;
-	for (i = 0; src[i] != '\0'; i++)
-	{
-		if (src[i] == '\n' || src[i] == '<' || src[i] == '>')
-			destsize += 4;
-		else if (src[i] == '&')
-			destsize += 5;
-		else if (src[i] == '"')
-			destsize += 6;
-		else if (src[i] != '\r')
-			destsize++;
-	}
-
-	dest = g_malloc(destsize);
-
-	/* Copy stuff, ignoring \r's, because they are dumb */
-	for (i = 0, j = 0; src[i] != '\0'; i++) {
-		if (src[i] == '\n') {
-			strcpy(&dest[j], "<BR>");
-			j += 4;
-		} else if (src[i] == '<') {
-			strcpy(&dest[j], "&lt;");
-			j += 4;
-		} else if (src[i] == '>') {
-			strcpy(&dest[j], "&gt;");
-			j += 4;
-		} else if (src[i] == '&') {
-			strcpy(&dest[j], "&amp;");
-			j += 5;
-		} else if (src[i] == '"') {
-			strcpy(&dest[j], "&quot;");
-			j += 6;
-		} else if (src[i] != '\r')
-			dest[j++] = src[i];
-	}
-
-	dest[destsize-1] = '\0';
-
-	return dest;
-}
-
-JsonParser *fb_get_parser(const gchar *data, gsize data_len)
-{
-	JsonParser *parser;
-
-	if (data == NULL) {
-		return NULL;
-	}
-
-	data = g_strstr_len(data, data_len, "for (;;);");
-	if (!data) {
-		return NULL;
-	} else {
-		data += strlen("for (;;);");
-	}
-
-	parser = json_parser_new();
-	if (!json_parser_load_from_data(parser, data, -1, NULL)) {
-		g_object_unref(parser);
-		return NULL;
-	}
-
-	return parser;
-}
-
-gint64 fb_time_kludge(gint initial_time)
-{
-	if (sizeof(gint) >= sizeof(gint64))
-		return initial_time;
-	
-	gint64 now_millis = (gint64) time(NULL);
-	now_millis *= 1000;
-	now_millis &= 0xFFFFFFFF00000000LL;
-	gint64 final_time = now_millis | initial_time;
-
-	return final_time;
-}
-
 /******************************************************************************/
 /* PRPL functions */
 /******************************************************************************/
@@ -242,10 +118,103 @@ static gboolean fb_get_messages_failsafe
 	return TRUE;
 }
 
+void fb_login_captcha_ok_cb(PurpleConnection *pc, PurpleRequestFields *fields)
+{
+	gint birthday_year, birthday_month, birthday_day;
+	gchar *postdata, *encoded_username, *encoded_password, *encoded_charset_test,
+			*encoded_auth_token, *encoded_persist_data;
+	const gchar* const *languages;
+	const gchar *locale;
+	FacebookAccount *fba = pc->proto_data;
+
+	birthday_year = purple_request_fields_get_integer(fields, "birthday_year");
+	birthday_month = purple_request_fields_get_integer(fields, "birthday_month");
+	birthday_day = purple_request_fields_get_integer(fields, "birthday_day");
+		
+	encoded_username = g_strdup(purple_url_encode(
+			purple_account_get_username(fba->account)));
+	encoded_password = g_strdup(purple_url_encode(
+			purple_account_get_password(fba->account)));
+	encoded_auth_token = g_strdup(purple_url_encode(
+			fba->auth_token));
+	encoded_persist_data = g_strdup(purple_url_encode(
+			fba->persist_data));
+	encoded_charset_test = g_strdup(purple_url_encode("EUR,?,EUR,?,?,?,?"));
+	languages = g_get_language_names();
+	locale = languages[0];
+	if (locale == NULL || g_str_equal(locale, "C"))
+		locale = "en_US";
+
+	postdata = g_strdup_printf(
+			"charset_test=%s&locale=%s&email=%s&pass=%s&persistent=1&login=Login&charset_test=%s&version=1.0&return_session=0&t_auth_token=%s&answered_captcha=1&captcha_persist_data=%s&birthday_captcha_day=%d&birthday_captcha_month=%d&birthday_captcha_year=%d",
+			encoded_charset_test, locale, encoded_username, encoded_password, encoded_charset_test, encoded_auth_token, encoded_persist_data, birthday_day, birthday_month, birthday_year);
+	g_free(encoded_username);
+	g_free(encoded_password);
+	g_free(encoded_charset_test);
+	g_free(encoded_auth_token);
+	g_free(encoded_persist_data);
+
+	fb_post_or_get(fba, FB_METHOD_POST | FB_METHOD_SSL, "login.facebook.com",
+			"/login.php?login_attempt=1", postdata, fb_login_cb, NULL, FALSE);
+	g_free(postdata);
+	
+	g_free(fba->auth_token);
+	g_free(fba->persist_data);
+	fba->auth_token = NULL;
+	fba->persist_data = NULL;
+}
+
 static void fb_login_cb(FacebookAccount *fba, gchar *response, gsize len,
 		gpointer userdata)
 {
 	gchar *user_cookie;
+	
+	if (len && g_strstr_len(response, len, "captcha"))
+	{
+		purple_connection_update_progress(fba->pc, _("Handling Captcha"), 2, 4);
+		
+		const gchar *persist_data_start = "<input type=\"hidden\" name=\"captcha_persist_data\" value=\"";
+		gchar *persist_data = g_strstr_len(response, len, persist_data_start);
+		if (persist_data)
+		{
+			persist_data += strlen(persist_data_start);
+			fba->persist_data = g_strndup(persist_data, strchr(persist_data, '"') - persist_data);
+		}
+		
+		const gchar *auth_token_start = "<input type=\"hidden\" name=\"t_auth_token\" value=\"";
+		gchar *auth_token = g_strstr_len(response, len, auth_token_start);
+		if (auth_token)
+		{
+			auth_token += strlen(auth_token);
+			fba->auth_token = g_strndup(auth_token, strchr(auth_token, '"') - auth_token);
+		}
+		
+		PurpleRequestFields *fields;
+		PurpleRequestFieldGroup *group;
+		PurpleRequestField *field;
+		
+		fields = purple_request_fields_new();
+		group = purple_request_field_group_new(NULL);
+		purple_request_fields_add_group(fields, group);
+		
+		field = purple_request_field_int_new("birthday_year", _("Year"), 0);
+		purple_request_field_group_add_field(group, field);
+		field = purple_request_field_int_new("birthday_month", _("Month"), 0);
+		purple_request_field_group_add_field(group, field);
+		field = purple_request_field_int_new("birthday_day", _("Day"), 0);
+		purple_request_field_group_add_field(group, field);
+		
+		purple_request_fields(fba->pc, 
+			_("Facebook Captcha"), _("Facebook Captcha"), 
+			_("Facebook thinks you're not you.  To prove you are, please enter your date of birth"), 
+			fields, 
+			_("OK"), G_CALLBACK(fb_login_captcha_ok_cb), 
+			_("Logout"), G_CALLBACK(fb_close), 
+			fba->account, NULL, NULL, fba->pc	 
+		);
+		
+		return;
+	}
 
 	purple_connection_update_progress(fba->pc, _("Authenticating"), 2, 3);
 
@@ -269,17 +238,12 @@ static void fb_login_cb(FacebookAccount 
 
 	/* This will kick off our long-poll message retrieval loop */
 	fb_get_post_form_id(fba);
-	fb_get_buddy_list(fba);
 	fb_check_friend_requests(fba);
 
 	/* periodically check for people adding you to their facebook friend list */
 	fba->friend_request_timer = purple_timeout_add_seconds(60 * 5,
 			fb_check_friend_requests, fba);
 
-	/* periodically check for updates to your buddy list */
-	fba->buddy_list_timer = purple_timeout_add_seconds(60,
-			fb_get_buddy_list, fba);
-
 	/* periodically check for new notifications */
 	fba->notifications_timer = purple_timeout_add_seconds(60,
 			(GSourceFunc)fb_get_notifications_feed, fba);
@@ -298,6 +262,9 @@ static void fb_login_cb(FacebookAccount 
 	fba->perpetual_messages_timer = purple_timeout_add_seconds(15,
 			(GSourceFunc)fb_get_messages_failsafe, fba);
 
+	/* init blist subsystem */
+	fb_blist_init(fba);
+
 	/* init conversation subsystem */
 	fb_conversation_init(fba);
 }
@@ -376,6 +343,9 @@ static void fb_close(PurpleConnection *p
 
 	purple_debug_info("facebook", "unloading plugin\n");
 
+	/* destroy blist subsystem */
+	fb_blist_destroy(fba);
+
 	/* destroy conversation subsystem */
 	fb_conversation_destroy(fba);
 
@@ -410,9 +380,6 @@ static void fb_close(PurpleConnection *p
 			postdata, NULL, NULL, FALSE);
 	g_free(postdata);
 
-	if (fba->buddy_list_timer) {
-		purple_timeout_remove(fba->buddy_list_timer);
-	}
 	if (fba->friend_request_timer) {
 		purple_timeout_remove(fba->friend_request_timer);
 	}
@@ -450,6 +417,8 @@ static void fb_close(PurpleConnection *p
 	g_free(fba->post_form_id);
 	g_free(fba->channel_number);
 	g_free(fba->last_status_message);
+	g_free(fba->auth_token);
+	g_free(fba->persist_data);
 	g_free(fba);
 }
 
@@ -518,7 +487,7 @@ static void fb_set_status_ok_cb(gpointer
 		postdata = g_strdup_printf("profile_id=%" G_GINT64_FORMAT "&clear=1&post_form_id=%s",
 				fba->uid, fba->post_form_id);
 
-	fb_post_or_get(fba, FB_METHOD_POST, NULL, "/updatestatus.php",
+	fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/updatestatus.php",
 			postdata, NULL, NULL, FALSE);
 
 	g_free(postdata);
@@ -567,6 +536,7 @@ static void fb_buddy_free(PurpleBuddy *b
 	}
 }
 
+#if PURPLE_MAJOR_VERSION >= 2 && PURPLE_MINOR_VERSION >= 5
 static GHashTable *fb_get_account_text_table(PurpleAccount *account)
 {
 	GHashTable *table;
@@ -577,6 +547,7 @@ static GHashTable *fb_get_account_text_t
 
 	return table;
 }
+#endif
 
 /******************************************************************************/
 /* Plugin functions */
@@ -615,6 +586,17 @@ static void fb_display_plugin_info(Purpl
 			_("Version"), FACEBOOK_PLUGIN_VERSION);
 }
 
+static void fb_refresh_blist(PurplePluginAction *action)
+{
+	PurpleConnection *pc;
+	FacebookAccount *fba;
+
+	pc = (PurpleConnection *) action->context;
+	fba = pc->proto_data;
+
+	fb_get_buddy_list(fba);
+}
+
 static GList *fb_actions(PurplePlugin *plugin, gpointer context)
 {
 	GList *m = NULL;
@@ -632,6 +614,11 @@ static GList *fb_actions(PurplePlugin *p
 			fb_search_users);
 	m = g_list_append(m, act);
 
+	// TODO: remove, this is for testing.  REMOVE.
+	act = purple_plugin_action_new(_("Refresh buddy list..."),
+			fb_refresh_blist);
+	m = g_list_append(m, act);
+
 	return m;
 }
 
@@ -646,8 +633,8 @@ static GList *fb_node_menu(PurpleBlistNo
 		buddy = (PurpleBuddy *)node;
 		
 		act = purple_menu_action_new(_("_Poke"),
-										PURPLE_CALLBACK(fb_blist_poke_buddy),
-										NULL, NULL);
+				PURPLE_CALLBACK(fb_blist_poke_buddy),
+				NULL, NULL);
 		m = g_list_append(m, act);
 	}
 	return m;
@@ -674,17 +661,35 @@ static void plugin_init(PurplePlugin *pl
 	PurplePluginProtocolInfo *prpl_info = info->extra_info;
 
 	/* Add options to the advanced screen in the account settings */
-	option = purple_account_option_bool_new(_("Hide myself in the Buddy List"), "facebook_hide_self", TRUE);
-	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
+	option = purple_account_option_bool_new(
+		_("Show history in new conversations"),
+		"facebook_show_history", TRUE);
+	prpl_info->protocol_options = g_list_append(
+		prpl_info->protocol_options, option);
 
-	option = purple_account_option_bool_new(_("Set Facebook status through Pidgin status"), "facebook_set_status_through_pidgin", FALSE);
-	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
+	option = purple_account_option_bool_new(
+		_("Hide myself in the Buddy List"),
+		"facebook_hide_self", TRUE);
+	prpl_info->protocol_options = g_list_append(
+		prpl_info->protocol_options, option);
 
-	option = purple_account_option_bool_new(_("Show Facebook notifications as e-mails in Pidgin"), "facebook_get_notifications", TRUE);
-	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
+	option = purple_account_option_bool_new(
+		_("Set Facebook status through Pidgin status"),
+		"facebook_set_status_through_pidgin", FALSE);
+	prpl_info->protocol_options = g_list_append(
+		prpl_info->protocol_options, option);
 
-	option = purple_account_option_bool_new(_("Edit Facebook friends from Pidgin"), "facebook_manage_friends", FALSE);
-	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
+	option = purple_account_option_bool_new(
+		_("Show Facebook notifications as e-mails in Pidgin"),
+		"facebook_get_notifications", TRUE);
+	prpl_info->protocol_options = g_list_append(
+		prpl_info->protocol_options, option);
+
+	option = purple_account_option_bool_new(
+		_("Edit Facebook friends from Pidgin"),
+		"facebook_manage_friends", FALSE);
+	prpl_info->protocol_options = g_list_append(
+		prpl_info->protocol_options, option);
 }
 
 static PurplePluginProtocolInfo prpl_info = {
@@ -714,7 +719,7 @@ static PurplePluginProtocolInfo prpl_inf
 	NULL,                   /* change_passwd */
 	fb_add_buddy,           /* add_buddy */
 	NULL,                   /* add_buddies */
-	NULL,                   /* remove_buddy */
+	fb_buddy_remove,        /* remove_buddy */
 	NULL,                   /* remove_buddies */
 	NULL,                   /* add_permit */
 	NULL,                   /* add_deny */
@@ -733,13 +738,13 @@ static PurplePluginProtocolInfo prpl_inf
 	NULL,                   /* get_cb_info */
 	NULL,                   /* get_cb_away */
 	NULL,                   /* alias_buddy */
-	NULL,                   /* group_buddy */
-	NULL,                   /* rename_group */
+	fb_group_buddy_move,    /* group_buddy */
+	fb_group_rename,        /* rename_group */
 	fb_buddy_free,          /* buddy_free */
 	fb_conversation_closed, /* convo_closed */
 	purple_normalize_nocase,/* normalize */
 	NULL,                   /* set_buddy_icon */
-	NULL,                   /* remove_group */
+	fb_group_remove,        /* remove_group */
 	NULL,                   /* get_cb_real_name */
 	NULL,                   /* set_chat_topic */
 	NULL,                   /* find_blist_chat */
@@ -756,8 +761,12 @@ static PurplePluginProtocolInfo prpl_inf
 	NULL,                   /* unregister_user */
 	NULL,                   /* send_attention */
 	NULL,                   /* attention_types */
+#if PURPLE_MAJOR_VERSION >= 2 && PURPLE_MINOR_VERSION >= 5
 	sizeof(PurplePluginProtocolInfo), /* struct_size */
 	fb_get_account_text_table, /* get_account_text_table */
+#else
+	(gpointer) sizeof(PurplePluginProtocolInfo)
+#endif
 };
 
 static PurplePluginInfo info = {
============================================================
--- libpurple/protocols/facebook/libfacebook.h	e5edab36948064137e8888d3aa9da077f34e2589
+++ libpurple/protocols/facebook/libfacebook.h	dbc2bfd413ba1089196a55882771e37b6da1bc35
@@ -21,7 +21,7 @@
 #ifndef LIBFACEBOOK_H
 #define LIBFACEBOOK_H
 
-#define FACEBOOK_PLUGIN_VERSION "1.53"
+#define FACEBOOK_PLUGIN_VERSION "1.60"
 #define FACEBOOK_PLUGIN_ID "prpl-bigbrownchunx-facebookim"
 
 #include <glib.h>
@@ -71,9 +71,11 @@
 #	include <zlib.h>
 #endif
 
-#define FB_MAX_MSG_RETRY 2
+#if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 12
+#	define atoll(a) g_ascii_strtoll(a, NULL, 0)
+#endif
 
-#include <json-glib/json-glib.h>
+#define FB_MAX_MSG_RETRY 2
 
 typedef struct _FacebookAccount FacebookAccount;
 typedef struct _FacebookBuddy FacebookBuddy;
@@ -88,11 +90,13 @@ struct _FacebookAccount {
 	GHashTable *cookie_table;
 	gchar *post_form_id;
 	gint64 uid;
-	guint buddy_list_timer;
+	guint buddy_list_timer; 		/* handled by fb_blist */
+	GHashTable *friend_lists;		/* handled by fb_friendlist */
+	GHashTable *friend_lists_reverse;	/* handled by fb_friendlist */
 	guint friend_request_timer;
 	gchar *channel_number;
 	guint message_fetch_sequence;
-	gint64 last_message_time;
+	gint64 last_message_time;		/* handled by fb_conversation */
 	GSList *resending_messages;
 	GHashTable *auth_buddies;
 	GHashTable *hostname_ip_cache;
@@ -104,6 +108,8 @@ struct _FacebookAccount {
 	gboolean is_idle;
 	GHashTable *sent_messages_hash;
 	gint last_inbox_count;
+	gchar *auth_token;
+	gchar *persist_data;
 };
 
 struct _FacebookBuddy {
@@ -116,10 +122,4 @@ struct _FacebookBuddy {
 	gchar *thumb_url;
 };
 
-/* TODO: move util functions into a utils file */
-gchar *fb_strdup_withhtml(const gchar *src);
-gchar *fb_convert_unicode(const gchar *input);
-JsonParser *fb_get_parser(const gchar *data, gsize data_len);
-gint64 fb_time_kludge(int initial_time);
-
 #endif /* LIBFACEBOOK_H */


More information about the Commits mailing list