adium: 83638905: pidgin-facebookchat at r508 (1.51+)
zacw at adiumx.com
zacw at adiumx.com
Sun Jun 21 13:45:27 EDT 2009
-----------------------------------------------------------------
Revision: 83638905971b97ae72bc7318a7f152adf5e8b6f1
Ancestor: 9e1d2fd991364612b0d97abc9dd223e9d234a36b
Author: zacw at adiumx.com
Date: 2009-06-21T17:18:15
Branch: im.pidgin.adium
URL: http://d.pidgin.im/viewmtn/revision/info/83638905971b97ae72bc7318a7f152adf5e8b6f1
Modified files:
libpurple/protocols/facebook/facebook.nsi
libpurple/protocols/facebook/fb_blist.c
libpurple/protocols/facebook/fb_connection.c
libpurple/protocols/facebook/fb_messages.c
libpurple/protocols/facebook/fb_messages.h
libpurple/protocols/facebook/fb_notifications.c
libpurple/protocols/facebook/libfacebook.c
libpurple/protocols/facebook/libfacebook.h
libpurple/protocols/facebook/pidgin-facebookchat.rc
libpurple/protocols/facebook/rss.xml
ChangeLog:
pidgin-facebookchat at r508 (1.51+)
-------------- next part --------------
============================================================
--- libpurple/protocols/facebook/facebook.nsi 3fcdc6328140c152a720ace476cd1a7922e7f1e4
+++ libpurple/protocols/facebook/facebook.nsi 67598b3e9ead8be15c1e600b6dd3de05c0dd682e
@@ -6,7 +6,7 @@ SetCompress off
; todo: SetBrandingImage
; HM NIS Edit Wizard helper defines
!define PRODUCT_NAME "pidgin-facebookchat"
-!define PRODUCT_VERSION "1.50"
+!define PRODUCT_VERSION "1.51"
!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}"
@@ -75,7 +75,10 @@ Section "MainSection" SEC01
cancel:
Abort "Installation of pidgin-facebookchat aborted"
after_copy:
-
+
+ SetOutPath "$PidginDir"
+ File "libjson-glib-1.0.dll"
+
SectionEnd
Function GetPidginInstPath
============================================================
--- libpurple/protocols/facebook/fb_blist.c 4a02f018ba3dac7cc7bc76cac9921751dacf09f3
+++ libpurple/protocols/facebook/fb_blist.c 5074f94eb56fc5bfe2337b0e991d3ef9d0f20361
@@ -22,10 +22,12 @@
#include "fb_connection.h"
#include "fb_blist.h"
-static void set_buddies_offline(PurpleBuddy *buddy, GSList *online_buddies_list)
+#include <json-glib/json-glib.h>
+
+static void set_buddies_offline(PurpleBuddy *buddy, GHashTable *online_buddies_list)
{
- if (g_slist_find(online_buddies_list, buddy) == NULL &&
- PURPLE_BUDDY_IS_ONLINE(buddy))
+ 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),
@@ -61,7 +63,7 @@ static void got_buddy_list_cb(FacebookAc
gsize data_len, gpointer userdata)
{
GSList *buddies_list;
- GSList *online_buddies_list = NULL;
+ GHashTable *online_buddies_list = g_hash_table_new(g_str_hash, g_str_equal);
PurpleBuddy *buddy;
FacebookBuddy *fbuddy;
gchar *uid;
@@ -72,17 +74,14 @@ static void got_buddy_list_cb(FacebookAc
gboolean idle;
guint32 error_number;
- gchar *search_start;
gchar *search_tmp;
gchar *tmp;
- gchar *largest_buddy_search_point = NULL;
PurpleGroup *fb_group = NULL;
gboolean current_buddy_online = FALSE;
purple_debug_info("facebook", "parsing buddy list\n");
- purple_debug_misc("facebook", "buddy list\n%s\n", data);
if (fba == NULL)
return;
@@ -93,6 +92,29 @@ static void got_buddy_list_cb(FacebookAc
_("Could not retrieve buddy list"));
return;
}
+
+ purple_debug_misc("facebook", "buddy list\n%s\n", data);
+
+ tmp = g_strstr_len(data, data_len, "for (;;);");
+ if (tmp)
+ {
+ tmp += strlen("for (;;);");
+ }
+
+ JsonParser *parser;
+ JsonNode *root;
+
+ parser = json_parser_new();
+ if(!json_parser_load_from_data(parser, tmp, -1, NULL))
+ {
+ purple_connection_error_reason(fba->pc,
+ PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+ _("Could not retrieve buddy list"));
+ return;
+ }
+ 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");
@@ -103,82 +125,79 @@ static void got_buddy_list_cb(FacebookAc
"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"}]} */
- tmp = g_strstr_len(data, data_len, "\"error\":");
- if (tmp != NULL)
+ if (json_object_has_member(objnode, "error"))
{
- tmp += 9;
- tmp = g_strndup(tmp, strchr(tmp, ',')-tmp);
- error_number = atoi(tmp);
- g_free(tmp);
+ error_number = json_node_get_int(json_object_get_member(objnode, "error"));
if (error_number)
{
- /* error :( */
- tmp = g_strstr_len(data, data_len, "\"errorDescription\":");
- tmp += 20;
- tmp = g_strndup(tmp, strchr(tmp, '"')-tmp);
- /* TODO: Use purple_connection_error_reason() */
- purple_connection_error(fba->pc, tmp);
- g_free(tmp);
+ purple_connection_error_reason(fba->pc,
+ PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+ json_node_dup_string(json_object_get_member(objnode, "errorDescription")));
+ g_object_unref(parser);
return;
}
}
/* look for "userInfos":{ ... }, */
- search_start = strstr(data, "\"userInfos\":{");
- if (search_start == NULL)
+ if (!json_object_has_member(objnode, "payload"))
+ {
+ g_object_unref(parser);
return;
- search_start += 13;
-
- while (*search_start != '}' && (search_start - data < data_len))
+ }
+ objnode = json_node_get_object(json_object_get_member(objnode, "payload"));
+ if (!json_object_has_member(objnode, "buddy_list"))
{
- tmp = strchr(search_start, ':');
- uid = g_strndup(search_start+1, tmp-search_start-2);
- /* purple_debug_misc("facebook", "uid: %s\n", uid); */
+ g_object_unref(parser);
+ return;
+ }
+ 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"));
+
+ 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"));
+ GList *userIds;
+ userIds = json_object_get_members(userInfos);
+ GList *currentUserNode;
+ for( currentUserNode = userIds;
+ currentUserNode;
+ currentUserNode = g_list_next(currentUserNode))
+ {
+ uid = currentUserNode->data;
+ purple_debug_misc("facebook", "uid: %s\n", uid);
- search_start += strlen(uid) + 2;
+ JsonObject *userInfo;
+ userInfo = json_node_get_object(json_object_get_member(userInfos, uid));
+ name = json_node_dup_string(json_object_get_member(userInfo, "name"));
+ purple_debug_misc("facebook", "name: %s\n", name);
- search_tmp = strstr(search_start, "\"name\":") + 8;
- if (search_tmp > largest_buddy_search_point)
- largest_buddy_search_point = search_tmp;
- search_tmp = g_strndup(search_tmp, strchr(search_tmp, '"')-search_tmp);
- name = fb_convert_unicode(search_tmp);
- g_free(search_tmp);
- /* purple_debug_misc("facebook", "name: %s\n", name); */
-
/* try updating the alias, just in case it was removed locally */
serv_got_alias(fba->pc, uid, name);
/* look for "uid":{"i":_____} */
- tmp = g_strdup_printf("\"%s\":{\"i\":", uid);
- search_tmp = g_strstr_len(data, data_len, tmp);
- if (search_tmp != NULL)
+ if (json_object_has_member(nowAvailableList, uid))
{
- search_tmp += strlen(tmp);
- if (search_tmp > largest_buddy_search_point)
- largest_buddy_search_point = search_tmp;
- search_tmp = g_strndup(search_tmp, strchr(search_tmp, '}')-search_tmp);
- /* purple_debug_misc("facebook", "buddy idle: %s\n", search_tmp); */
- buddy = purple_find_buddy(fba->account, uid);
- idle = g_str_equal(search_tmp, "true");
- g_free(search_tmp);
+ JsonObject *userBlistInfo;
+ userBlistInfo = json_node_get_object(json_object_get_member(nowAvailableList, uid));
+ idle = json_node_get_boolean(json_object_get_member(userBlistInfo, "i"));
+ purple_debug_misc("facebook", "buddy idle: %s\n", (idle?"true":"false"));
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;
}
- g_free(tmp);
/* Set the buddy status text and time */
- search_tmp = strstr(search_start, "\"status\":");
- if (search_tmp != NULL && *(search_tmp + 9) == '"')
+ if (json_object_has_member(userInfo, "status"))
{
- search_tmp += 10;
- if (search_tmp > largest_buddy_search_point)
- largest_buddy_search_point = strstr(search_tmp, ",\"statusTime");
- search_tmp = g_strndup(search_tmp, strstr(search_tmp, ",\"statusTime")-1-search_tmp);
- status_text = fb_convert_unicode(search_tmp);
- g_free(search_tmp);
+ status_text = json_node_dup_string(json_object_get_member(userInfo, "status"));
} else {
status_text = NULL;
}
@@ -197,12 +216,6 @@ static void got_buddy_list_cb(FacebookAc
{
g_free(status_text);
g_free(name);
- g_free(uid);
-
- /* Move pointer to the end of the buddy entry */
- search_start = strchr(largest_buddy_search_point, '}') + 1;
- while (*search_start == ',' && (search_start - data < data_len))
- search_start++;
/* go on to the next buddy */
continue;
} else {
@@ -244,7 +257,6 @@ static void got_buddy_list_cb(FacebookAc
fbuddy = buddy->proto_data;
}
- g_free(uid);
g_free(name);
if (status_text != NULL)
@@ -252,16 +264,10 @@ static void got_buddy_list_cb(FacebookAc
tmp = fb_strdup_withhtml(status_text);
g_free(status_text);
status_text = tmp;
- /* purple_debug_misc("facebook", "status: %s\n", status_text); */
+ purple_debug_misc("facebook", "status: %s\n", status_text);
- search_tmp = strstr(search_start, "\"statusTimeRel\":") + 17;
- if (search_tmp > largest_buddy_search_point)
- largest_buddy_search_point = strchr(search_tmp, '"');
- search_tmp = g_strndup(search_tmp, strchr(search_tmp, '"')-search_tmp);
- status_time_text = fb_convert_unicode(search_tmp);
- g_free(search_tmp);
-
- if (g_str_equal(status_time_text, "ull,"))
+ 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;
@@ -270,8 +276,7 @@ static void got_buddy_list_cb(FacebookAc
if (status_time_text != NULL)
{
fbuddy->status_rel_time = fb_strdup_withhtml(status_time_text);
- g_free(status_time_text);
- /* purple_debug_misc("facebook", "status time: %s\n", fbuddy->status_rel_time); */
+ purple_debug_misc("facebook", "status time: %s\n", fbuddy->status_rel_time);
} else {
fbuddy->status_rel_time = NULL;
}
@@ -301,10 +306,7 @@ static void got_buddy_list_cb(FacebookAc
}
/* Set the buddy icon (if it hasn't changed) */
- search_tmp = strstr(search_start, "\"thumbSrc\":") + 12;
- if (search_tmp > largest_buddy_search_point)
- largest_buddy_search_point = search_tmp;
- buddy_icon_url = g_strndup(search_tmp, strchr(search_tmp, '"')-search_tmp);
+ 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);
@@ -345,20 +347,14 @@ static void got_buddy_list_cb(FacebookAc
if (current_buddy_online)
{
/* Add buddy to the list of online buddies */
- online_buddies_list = g_slist_append(online_buddies_list, buddy);
+ 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);
}
-
- /* Move pointer after any user configurable data */
- search_start = search_tmp;
- /* Move pointer to the end of the buddy entry */
- search_start = strchr(largest_buddy_search_point, '}') + 1;
- while (*search_start == ',' && (search_start - data < data_len))
- search_start++;
}
+ g_list_free(userIds);
buddies_list = purple_find_buddies(fba->account, NULL);
if (buddies_list != NULL)
@@ -366,7 +362,25 @@ static void got_buddy_list_cb(FacebookAc
g_slist_foreach(buddies_list, (GFunc)set_buddies_offline, online_buddies_list);
g_slist_free(buddies_list);
}
- g_slist_free(online_buddies_list);
+ g_hash_table_destroy(online_buddies_list);
+
+ if (notifications != NULL)
+ {
+ 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);
+ }
+ }
+ }
+
+ g_object_unref(parser);
}
gboolean fb_get_buddy_list(gpointer data)
@@ -377,7 +391,7 @@ gboolean fb_get_buddy_list(gpointer data
fba = data;
postdata = g_strdup_printf(
- "user=%d&popped_out=true&force_render=true&buddy_list=1",
+ "user=%d&popped_out=true&force_render=true&buddy_list=1¬ifications=1",
fba->uid);
fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/presence/update.php",
postdata, got_buddy_list_cb, NULL, FALSE);
============================================================
--- libpurple/protocols/facebook/fb_connection.c 9a80b89fd6fe38724437f7ac53723b107442e459
+++ libpurple/protocols/facebook/fb_connection.c 8668d97080d9afe72ca0519bb5f0ab641daa34c5
@@ -23,40 +23,80 @@ static void fb_attempt_connection(Facebo
static void fb_attempt_connection(FacebookConnection *);
#ifdef HAVE_ZLIB
-static guchar *fb_gunzip(const guchar *gzip_data, ssize_t *len_ptr)
+
+static gchar *fb_gunzip(const guchar *gzip_data, ssize_t *len_ptr)
{
gsize gzip_data_len = *len_ptr;
z_stream zstr;
int gzip_err = 0;
- guchar *output_data;
+ gchar *data_buffer;
gulong gzip_len = G_MAXUINT16;
+ GString *output_string = NULL;
- g_return_val_if_fail(zlib_inflate != NULL, NULL);
+ data_buffer = g_new0(gchar, gzip_len);
- output_data = g_new0(guchar, gzip_len);
-
- zstr.next_in = gzip_data;
- zstr.avail_in = gzip_data_len;
+ zstr.next_in = NULL;
+ zstr.avail_in = 0;
zstr.zalloc = Z_NULL;
zstr.zfree = Z_NULL;
- zstr.opaque = Z_NULL;
- int flags = gzip_data[3];
- int offset = 4;
- /* if (flags & 0x04) offset += *tmp[] */
- zstr.next_in += offset;
- zstr.avail_in -= offset;
- zlib_inflateInit2_(&zstr, -MAX_WBITS, ZLIB_VERSION, sizeof(z_stream));
- zstr.next_out = output_data;
+ zstr.opaque = 0;
+ gzip_err = inflateInit2(&zstr, MAX_WBITS+32);
+ if (gzip_err != Z_OK)
+ {
+ g_free(data_buffer);
+ purple_debug_error("facebook", "no built-in gzip support in zlib\n");
+ return NULL;
+ }
+
+ zstr.next_in = (Bytef *)gzip_data;
+ zstr.avail_in = gzip_data_len;
+
+ zstr.next_out = (Bytef *)data_buffer;
zstr.avail_out = gzip_len;
- gzip_err = zlib_inflate(&zstr, Z_FINISH);
- zlib_inflateEnd(&zstr);
+
+ gzip_err = inflate(&zstr, Z_SYNC_FLUSH);
- purple_debug_info("facebook", "gzip len: %ld, len: %ld\n", gzip_len,
- gzip_data_len);
- purple_debug_info("facebook", "gzip flags: %d\n", flags);
- purple_debug_info("facebook", "gzip error: %d\n", gzip_err);
+ if (gzip_err == Z_DATA_ERROR)
+ {
+ inflateEnd(&zstr);
+ inflateInit2(&zstr, -MAX_WBITS);
+ if (gzip_err != Z_OK)
+ {
+ g_free(data_buffer);
+ purple_debug_error("facebook", "Cannot decode gzip header\n");
+ return NULL;
+ }
+ zstr.next_in = (Bytef *)gzip_data;
+ zstr.avail_in = gzip_data_len;
+ zstr.next_out = (Bytef *)data_buffer;
+ zstr.avail_out = gzip_len;
+ gzip_err = inflate(&zstr, Z_SYNC_FLUSH);
+ }
+ output_string = g_string_new("");
+ while (gzip_err == Z_OK)
+ {
+ //append data to buffer
+ output_string = g_string_append_len(output_string, data_buffer, gzip_len - zstr.avail_out);
+ //reset buffer pointer
+ zstr.next_out = (Bytef *)data_buffer;
+ zstr.avail_out = gzip_len;
+ gzip_err = inflate(&zstr, Z_SYNC_FLUSH);
+ }
+ if (gzip_err == Z_STREAM_END)
+ {
+ output_string = g_string_append_len(output_string, data_buffer, gzip_len - zstr.avail_out);
+ } else {
+ purple_debug_error("facebook", "gzip inflate error\n");
+ }
+ inflateEnd(&zstr);
- *len_ptr = gzip_len;
+ g_free(data_buffer);
+
+ gchar *output_data = g_strdup(output_string->str);
+ *len_ptr = output_string->len;
+
+ g_string_free(output_string, TRUE);
+
return output_data;
}
#endif
@@ -152,13 +192,10 @@ static void fb_connection_process_data(F
if (strstr(fbconn->rx_buf, "Content-Encoding: gzip"))
{
/* we've received compressed gzip data, decompress */
- if (zlib_inflate != NULL)
- {
- gchar *gunzipped;
- gunzipped = fb_gunzip((const guchar *)tmp, &len);
- g_free(tmp);
- tmp = gunzipped;
- }
+ gchar *gunzipped;
+ gunzipped = fb_gunzip((const guchar *)tmp, &len);
+ g_free(tmp);
+ tmp = gunzipped;
}
#endif
}
@@ -427,6 +464,9 @@ void fb_post_or_get(FacebookAccount *fba
const gchar *user_agent;
const gchar* const *languages;
gchar *language_names;
+ PurpleProxyInfo *proxy_info = NULL;
+ gchar *proxy_auth;
+ gchar *proxy_auth_base64;
/* TODO: Fix keepalive and use it as much as possible */
keepalive = FALSE;
@@ -434,28 +474,36 @@ void fb_post_or_get(FacebookAccount *fba
if (host == NULL)
host = "www.facebook.com";
- if (fba && fba->account && fba->account->proxy_info &&
- (fba->account->proxy_info->type == PURPLE_PROXY_HTTP ||
- (fba->account->proxy_info->type == PURPLE_PROXY_USE_GLOBAL &&
- purple_global_proxy_get_info() &&
- purple_global_proxy_get_info()->type ==
- PURPLE_PROXY_HTTP)))
+ if (fba && fba->account && !(method & FB_METHOD_SSL))
{
+ proxy_info = purple_proxy_get_setup(fba->account);
+ if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_USE_GLOBAL)
+ proxy_info = purple_global_proxy_get_info();
+ if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_HTTP)
+ {
+ is_proxy = TRUE;
+ }
+ }
+ if (is_proxy == TRUE)
+ {
real_url = g_strdup_printf("http://%s%s", host, url);
- is_proxy = TRUE;
} else {
real_url = g_strdup(url);
}
cookies = fb_cookies_to_string(fba);
user_agent = purple_account_get_string(fba->account, "user-agent", "Opera/9.50 (Windows NT 5.1; U; en-GB)");
+
+ if (method & FB_METHOD_POST && !postdata)
+ postdata = "";
/* Build the request */
request = g_string_new(NULL);
g_string_append_printf(request, "%s %s HTTP/1.0\r\n",
(method & FB_METHOD_POST) ? "POST" : "GET",
real_url);
- g_string_append_printf(request, "Host: %s\r\n", host);
+ if (is_proxy == FALSE)
+ g_string_append_printf(request, "Host: %s\r\n", host);
g_string_append_printf(request, "Connection: %s\r\n",
(keepalive ? "Keep-Alive" : "close"));
g_string_append_printf(request, "User-Agent: %s\r\n", user_agent);
@@ -468,9 +516,20 @@ void fb_post_or_get(FacebookAccount *fba
g_string_append_printf(request, "Accept: */*\r\n");
g_string_append_printf(request, "Cookie: isfbe=false;%s\r\n", cookies);
#ifdef HAVE_ZLIB
- if (zlib_inflate != NULL)
- g_string_append_printf(request, "Accept-Encoding: gzip\r\n");
+ g_string_append_printf(request, "Accept-Encoding: gzip\r\n");
#endif
+ if (is_proxy == TRUE)
+ {
+ if (purple_proxy_info_get_username(proxy_info) &&
+ purple_proxy_info_get_password(proxy_info))
+ {
+ proxy_auth = g_strdup_printf("%s:%s", purple_proxy_info_get_username(proxy_info), purple_proxy_info_get_password(proxy_info));
+ proxy_auth_base64 = purple_base64_encode((guchar *)proxy_auth, strlen(proxy_auth));
+ g_string_append_printf(request, "Proxy-Authorization: Basic %s\r\n", proxy_auth_base64);
+ g_free(proxy_auth_base64);
+ g_free(proxy_auth);
+ }
+ }
/* Tell the server what language we accept, so that we get error messages in our language (rather than our IP's) */
languages = g_get_language_names();
============================================================
--- libpurple/protocols/facebook/fb_messages.c 09af6820f46c993c5874e2765e3d40831096b7ee
+++ libpurple/protocols/facebook/fb_messages.c f5bc0e080df2dcc102667479e7940791008251bd
@@ -21,6 +21,8 @@
#include "fb_messages.h"
#include "fb_connection.h"
+#include <json-glib/json-glib.h>
+
typedef struct _FacebookOutgoingMessage FacebookOutgoingMessage;
struct _FacebookOutgoingMessage {
@@ -30,14 +32,47 @@ struct _FacebookOutgoingMessage {
gchar *message;
gint msg_id;
guint retry_count;
+ guint resend_timer;
};
static gboolean fb_send_im_fom(FacebookOutgoingMessage *msg);
+static gboolean fb_resend_im_fom(FacebookOutgoingMessage *msg);
static gboolean fb_get_new_messages(FacebookAccount *fba);
+static gchar *fb_replace_styled_text(const gchar *text);
+static FacebookOutgoingMessage *fb_msg_create(FacebookAccount *fba)
+{
+ FacebookOutgoingMessage *msg;
+
+ msg = g_new0(FacebookOutgoingMessage, 1);
+ msg->fba = fba;
+
+ return msg;
+}
+
+static void fb_msg_destroy(FacebookOutgoingMessage *msg)
+{
+ if (msg->resend_timer) {
+ purple_timeout_remove(msg->resend_timer);
+ }
+ g_free(msg->who);
+ g_free(msg->message);
+ g_free(msg);
+}
+
+void fb_cancel_resending_messages(FacebookAccount *fba)
+{
+ while (fba->resending_messages != NULL) {
+ FacebookOutgoingMessage *msg = fba->resending_messages->data;
+ fba->resending_messages = g_slist_remove(fba->resending_messages, msg);
+ fb_msg_destroy(msg);
+ }
+}
+
static void got_new_messages(FacebookAccount *fba, gchar *data,
gsize data_len, gpointer userdata)
{
+ gchar *message;
gchar *message_text;
gchar *message_time;
gchar *from;
@@ -106,14 +141,14 @@ static void got_new_messages(FacebookAcc
purple_debug_error("facebook",
"got data back, but it's not even json\n");
- purple_timeout_add_seconds(1, (GSourceFunc)fb_get_new_messages, fba);
+ fb_get_new_messages(fba);
return;
}
- /* refresh means that the session or post_form_id is invalid */
+ /* refresh means that the channel is invalid */
if (g_str_equal(data, "for (;;);{\"t\":\"refresh\"}"))
{
- purple_timeout_add_seconds(1, (GSourceFunc)fb_get_post_form_id, fba);
+ fb_reconnect(fba);
return;
}
@@ -121,7 +156,7 @@ static void got_new_messages(FacebookAcc
if (g_str_equal(data, "for (;;);{\"t\":\"continue\"}"))
{
/* Continue looping, waiting for more messages */
- purple_timeout_add_seconds(1, (GSourceFunc)fb_get_new_messages, fba);
+ fb_get_new_messages(fba);
return;
}
@@ -188,8 +223,6 @@ static void got_new_messages(FacebookAcc
if (from && to && g_str_equal(type, "msg"))
{
/* IM message */
- if (fba->uid != atoi(from) || fba->uid == atoi(to))
- {
tmp = strstr(start, "\"msgID\":");
tmp += 9;
tmp = g_strndup(tmp, strchr(tmp, '"') - tmp);
@@ -215,9 +248,10 @@ static void got_new_messages(FacebookAcc
tmp = strstr(start, "\"text\":\"");
tmp += 8;
tmp = g_strndup(tmp, strstr(tmp, "\",\"time\":") - tmp);
- message_text = fb_convert_unicode(tmp);
+ message = fb_convert_unicode(tmp);
g_free(tmp);
- tmp = fb_strdup_withhtml(message_text);
+ message_text = fb_strdup_withhtml(message);
+ tmp = fb_replace_styled_text(message_text);
g_free(message_text);
message_text = tmp;
purple_debug_info("facebook", "text: %s\n", message_text);
@@ -240,10 +274,13 @@ static void got_new_messages(FacebookAcc
g_free(tmp);
}
}
-
- serv_got_im(pc, from, message_text, PURPLE_MESSAGE_RECV, atoi(message_time));
-
+ if (fba->uid != atoi(from) || fba->uid == atoi(to))
+ serv_got_im(pc, from, message_text, PURPLE_MESSAGE_RECV, atoi(message_time));
+ else if (!g_hash_table_remove(fba->sent_messages_hash, message))
+ serv_got_im(pc, to, message_text, PURPLE_MESSAGE_SEND, atoi(message_time));
+ g_free(message);
+
/*
* Acknowledge receipt of the message by simulating
* focusing the window. Not sure what the window_id
@@ -251,7 +288,7 @@ static void got_new_messages(FacebookAcc
* something internal to the Facebook javascript that
* is used for maintaining UI state across page loads?
*/
- if (!fba->is_idle)
+ if (!fba->is_idle && fba->uid != atoi(from))
{
gchar *postdata;
@@ -267,7 +304,7 @@ static void got_new_messages(FacebookAcc
g_free(message_text);
g_free(message_time);
}
- }
+
start = strchr(start, '}')+1;
} else if (from && g_str_equal(type, "typ"))
{
@@ -366,10 +403,14 @@ static void fb_send_im_cb(FacebookAccoun
FacebookOutgoingMessage *msg = user_data;
gchar *error_summary = NULL;
gchar *tmp;
+ gboolean was_an_error = FALSE;
/* NULL data crashes on Windows */
if (data == NULL)
+ {
+ was_an_error = TRUE;
data = "(null)";
+ }
purple_debug_misc("facebook", "sent im response: %s\n", data);
/* for (;;);{"error":1356003,"errorSummary":"Send destination not online",
@@ -399,7 +440,8 @@ static void fb_send_im_cb(FacebookAccoun
/* there was an error, either report it or retry */
if (msg->retry_count++ < FB_MAX_MSG_RETRY)
{
- purple_timeout_add_seconds(1, (GSourceFunc)fb_send_im_fom, msg);
+ msg->resend_timer = purple_timeout_add_seconds(1, (GSourceFunc)fb_resend_im_fom, msg);
+ fba->resending_messages = g_slist_prepend(fba->resending_messages, msg);
g_free(error_summary);
return;
}
@@ -410,15 +452,19 @@ static void fb_send_im_cb(FacebookAccoun
fba->account, msg->who);
purple_conversation_write(conv, NULL, error_summary,
PURPLE_MESSAGE_ERROR, msg->time);
+ was_an_error = TRUE;
}
}
}
}
+ if (was_an_error)
+ {
+ g_hash_table_remove(fba->sent_messages_hash, msg->message);
+ }
+
g_free(error_summary);
- g_free(msg->who);
- g_free(msg->message);
- g_free(msg);
+ fb_msg_destroy(msg);
}
static gboolean fb_send_im_fom(FacebookOutgoingMessage *msg)
@@ -439,19 +485,25 @@ static gboolean fb_send_im_fom(FacebookO
return FALSE;
}
+static gboolean fb_resend_im_fom(FacebookOutgoingMessage *msg)
+{
+ msg->fba->resending_messages = g_slist_remove(msg->fba->resending_messages, msg);
+
+ return fb_send_im_fom(msg);
+}
+
int fb_send_im(PurpleConnection *pc, const gchar *who, const gchar *message, PurpleMessageFlags flags)
{
FacebookOutgoingMessage *msg;
+ FacebookAccount *fba = pc->proto_data;
- msg = g_new0(FacebookOutgoingMessage, 1);
- msg->fba = pc->proto_data;
+ msg = fb_msg_create(fba);
/* convert html to plaintext, removing trailing spaces */
msg->message = purple_markup_strip_html(message);
if (strlen(msg->message) > 999)
{
- g_free(msg->message);
- g_free(msg);
+ fb_msg_destroy(msg);
return -E2BIG;
}
@@ -460,16 +512,94 @@ int fb_send_im(PurpleConnection *pc, con
msg->time = time(NULL);
msg->retry_count = 0;
+ //save that we're sending the message
+ g_hash_table_insert(fba->sent_messages_hash, strdup(msg->message), NULL);
+
fb_send_im_fom(msg);
- return strlen(message);
+ /* gchar *styled_message = fb_replace_styled_text(msg->message);
+ serv_got_im(pc, msg->who, styled_message, PURPLE_MESSAGE_SEND, msg->time);
+ g_free(styled_message);
+
+ return 0; */
+ return 1;
}
+void got_reconnect_json(FacebookAccount *fba, gchar *data, gsize data_len, gpointer userdata)
+{
+ gchar *new_channel_number;
+
+ JsonParser *parser;
+ JsonNode *root;
+
+ if (data == NULL)
+ data = "(null)";
+
+ gchar *tmp = g_strstr_len(data, data_len, "for (;;);");
+ if (tmp)
+ {
+ tmp += strlen("for (;;);");
+ }
+
+ parser = json_parser_new();
+ if(!json_parser_load_from_data(parser, tmp, -1, NULL))
+ {
+ purple_debug_error("facebook", "couldn't parse reconnect data\n");
+ purple_debug_info("facebook", "page content: %s\n", data);
+ purple_connection_error_reason(fba->pc,
+ PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+ _("Chat service currently unavailable"));
+ g_object_unref(parser);
+ return;
+ }
+ root = json_parser_get_root(parser);
+ JsonObject *objnode;
+ objnode = json_node_get_object(root);
+
+ JsonObject *payload = json_node_get_object(json_object_get_member(objnode, "payload"));
+
+ /* eg {"host":"channel01"} */
+ const gchar *new_channel_host = json_node_get_string(json_object_get_member(payload, "host"));
+
+ if (new_channel_host == NULL)
+ {
+ purple_debug_error("facebook", "couldn't find new channel number\n");
+ purple_debug_info("facebook", "page content: %s\n", data);
+ purple_connection_error_reason(fba->pc,
+ PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+ _("Chat service currently unavailable"));
+ g_object_unref(parser);
+ return;
+ }
+
+ new_channel_number = g_strdup(&new_channel_host[7]);
+ g_free(fba->channel_number);
+ fba->channel_number = new_channel_number;
+
+ gint new_seq = json_node_get_int(json_object_get_member(payload, "seq"));
+ fba->message_fetch_sequence = new_seq;
+
+ /*
+ * Now that we have a channel number we can start looping and
+ * waiting for messages
+ */
+ fb_get_new_messages(fba);
+ g_object_unref(parser);
+}
+
+gboolean fb_reconnect(FacebookAccount *fba)
+{
+ gchar *url = g_strdup_printf("/ajax/presence/reconnect.php?post_form_id=%s", fba->post_form_id);
+ fb_post_or_get(fba, FB_METHOD_GET, NULL, url, NULL, got_reconnect_json, NULL, FALSE);
+ g_free(url);
+
+ return FALSE;
+}
+
static void got_form_id_page(FacebookAccount *fba, gchar *data, gsize data_len, gpointer userdata)
{
const gchar *start_text = "id=\"post_form_id\" name=\"post_form_id\" value=\"";
gchar *post_form_id;
- gchar *channel_number;
gchar *tmp = NULL;
/* NULL data crashes on Windows */
@@ -493,41 +623,13 @@ static void got_form_id_page(FacebookAcc
g_free(fba->post_form_id);
fba->post_form_id = post_form_id;
- /* search for channel server number. we might want to use
- * /ajax/presence/reconnect.php in the future */
- start_text = "\", \"channel";
- tmp = g_strstr_len(data, data_len, start_text);
- if (tmp == NULL)
- {
- /* Some proxies strip whitepsace */
- start_text = "\",\"channel";
- tmp = g_strstr_len(data, data_len, start_text);
- if (tmp == NULL)
- {
- purple_debug_error("facebook", "couldn't find channel\n");
- purple_debug_misc("facebook", "page content: %s\n", data);
- purple_connection_error_reason(fba->pc,
- PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
- _("Chat service currently unavailable."));
- return;
- }
- }
-
- tmp += strlen(start_text);
- channel_number = g_strndup(tmp, strchr(tmp, '"') - tmp);
-
- g_free(fba->channel_number);
- fba->channel_number = channel_number;
tmp = g_strdup_printf("visibility=true&post_form_id=%s", post_form_id);
fb_post_or_get(fba, FB_METHOD_POST, "apps.facebook.com", "/ajax/chat/settings.php", tmp, NULL, NULL, FALSE);
g_free(tmp);
-
- /*
- * Now that we have a channel number we can start looping and
- * waiting for messages
- */
- fb_get_new_messages(fba);
+
+ /* Grab new channel number */
+ fb_reconnect(fba);
}
gboolean fb_get_post_form_id(FacebookAccount *fba)
@@ -535,3 +637,33 @@ gboolean fb_get_post_form_id(FacebookAcc
fb_post_or_get(fba, FB_METHOD_GET, NULL, "/presence/popout.php", NULL, got_form_id_page, NULL, FALSE);
return FALSE;
}
+
+/* Converts *text* into <b>text</b> and _text_ into <i>text</i> */
+static gchar *fb_replace_styled_text(const gchar *text)
+{
+ if (glib_check_version(2, 14, 0))
+ {
+ return g_strdup(text);
+ } else {
+ static GRegex *underline_regex = NULL;
+ static GRegex *bold_regex = NULL;
+ gchar *midway_string;
+ gchar *output_string;
+
+ if (underline_regex == NULL)
+ {
+ underline_regex = g_regex_new("\\b_([^_\\*]+)_\\b", G_REGEX_OPTIMIZE, 0, NULL);
+ }
+ if (bold_regex == NULL)
+ {
+ bold_regex = g_regex_new("(\\s|^)\\*([^_\\*]+)\\*(?=$|\\s)", G_REGEX_OPTIMIZE, 0, NULL);
+ }
+
+ midway_string = g_regex_replace(underline_regex, text, -1, 0, "<u>\\1</u>", 0, NULL);
+ output_string = g_regex_replace(bold_regex, midway_string, -1, 0, "\\1<b>\\2</b>", 0, NULL);
+ g_free(midway_string);
+
+ return output_string;
+ }
+}
+
============================================================
--- libpurple/protocols/facebook/fb_messages.h 44345656bb5ce15bc8a1a50d66aaa6ed88426dff
+++ libpurple/protocols/facebook/fb_messages.h aaab76c0a6d161f4e6e522833e4aae633a952997
@@ -24,7 +24,10 @@ gboolean fb_get_post_form_id(FacebookAcc
#include "libfacebook.h"
gboolean fb_get_post_form_id(FacebookAccount *fba);
+gboolean fb_reconnect(FacebookAccount *fba);
int fb_send_im(PurpleConnection *pc, const gchar *who, const gchar *message,
PurpleMessageFlags flags);
+void fb_cancel_resending_messages(FacebookAccount *fba);
+
#endif /* FACEBOOK_MESSAGES_H */
============================================================
--- libpurple/protocols/facebook/fb_notifications.c bb4c47d05900af3060131bb8ba4028beadcb8300
+++ libpurple/protocols/facebook/fb_notifications.c 1066c720340c2b996497a69a99de13e422bf285b
@@ -143,33 +143,20 @@ static void find_feed_url_cb(FacebookAcc
static void find_feed_url_cb(FacebookAccount *fba, gchar *data, gsize data_len, gpointer userdata)
{
- const gchar *search_string = "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"Your "Facebook Notifications Feed\" href=\"";
- const gchar *search_string2 = "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"Your &quot;Facebook Notifications Feed\" href=\"";
- const gchar *search_string3 = "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"Your &quot;Facebook Notifications&quot; Feed\" href=\"";
+ const gchar *search_string = "/feeds/notifications.php";
gchar *feed_url;
gchar *stripped;
purple_debug_info("facebook", "find_feed_url_cb\n");
+ if (!data)
+ data = "(null)";
+
feed_url = g_strstr_len(data, data_len, search_string);
- if (feed_url)
+ if (!feed_url)
{
- feed_url += strlen(search_string);
- } else {
- feed_url = g_strstr_len(data, data_len, search_string2);
- if (feed_url)
- {
- feed_url += strlen(search_string2);
- } else {
- feed_url = g_strstr_len(data, data_len, search_string3);
- if (feed_url)
- {
- feed_url += strlen(search_string3);
- } else {
- purple_debug_error("facebook", "received data, but could not find url on page\n");
- return;
- }
- }
+ purple_debug_error("facebook", "received data, but could not find url on page\n");
+ return;
}
feed_url = g_strndup(feed_url, strchr(feed_url, '"') - feed_url);
@@ -177,10 +164,8 @@ static void find_feed_url_cb(FacebookAcc
/* convert & to & */
stripped = purple_unescape_html(feed_url);
g_free(feed_url);
- /* strip the host and protocol off url */
- feed_url = g_strdup(strstr(stripped, "/feeds"));
- g_free(stripped);
-
+ feed_url = stripped;
+
purple_debug_info("facebook", "parsed feed url %s\n", feed_url);
if (feed_url && *feed_url)
============================================================
--- libpurple/protocols/facebook/libfacebook.c 171834be239f664301869ffb3a517a1cc548fa56
+++ libpurple/protocols/facebook/libfacebook.c 343796cc30236c62ce8522c5fe370f43924f7194
@@ -262,7 +262,9 @@ static void fb_login(PurpleAccount *acco
FacebookAccount *fba;
guint16 i;
gchar *postdata, *encoded_username, *encoded_password, *encoded_charset_test;
-
+ const gchar* const *languages;
+ const gchar *locale;
+
/* Create account and initialize state */
fba = g_new0(FacebookAccount, 1);
fba->account = account;
@@ -273,6 +275,8 @@ static void fb_login(PurpleAccount *acco
g_free, g_free);
fba->hostname_ip_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, g_free);
+ fba->sent_messages_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, NULL);
g_hash_table_replace(fba->cookie_table, g_strdup("test_cookie"),
g_strdup("1"));
@@ -298,16 +302,20 @@ static void fb_login(PurpleAccount *acco
encoded_password = g_strdup(purple_url_encode(
purple_account_get_password(fba->account)));
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(
- "email=%s&pass=%s&persistent=1&login=Login&charset_test=%s",
- encoded_username, encoded_password, encoded_charset_test);
+ "charset_test=%s&locale=%s&email=%s&pass=%s&pass_placeHolder=Password&persistent=1&login=Login&charset_test=%s",
+ encoded_charset_test, locale, encoded_username, encoded_password, encoded_charset_test);
g_free(encoded_username);
g_free(encoded_password);
g_free(encoded_charset_test);
fb_post_or_get(fba, FB_METHOD_POST | FB_METHOD_SSL, "login.facebook.com",
- "/login.php", postdata, fb_login_cb, NULL, FALSE);
+ "/login.php?login_attempt=1", postdata, fb_login_cb, NULL, FALSE);
g_free(postdata);
}
@@ -383,6 +391,10 @@ static void fb_close(PurpleConnection *p
fba->dns_queries = g_slist_remove(fba->dns_queries, dns_query);
purple_dnsquery_destroy(dns_query);
}
+
+ if (fba->resending_messages != NULL) {
+ fb_cancel_resending_messages(fba);
+ }
g_hash_table_destroy(fba->cookie_table);
g_hash_table_destroy(fba->hostname_ip_cache);
@@ -541,38 +553,11 @@ static gboolean plugin_load(PurplePlugin
static gboolean plugin_load(PurplePlugin *plugin)
{
-#ifdef HAVE_ZLIB
- /* try dynamically loading zlib functions */
- if (zlib_library == NULL)
- /* zlib_library = dlopen("zlib1.dll", RTLD_LAZY); */
- zlib_library = dlopen("libz.dll", RTLD_LAZY);
- if (zlib_library == NULL)
- zlib_library = dlopen("libz.so", RTLD_LAZY);
- if (zlib_library == NULL)
- zlib_library = dlopen("libz.dylib", RTLD_LAZY);
- if (zlib_inflate == NULL && zlib_library != NULL)
- {
- zlib_inflate = (int (*)()) dlsym(zlib_library, "inflate");
- zlib_inflateInit2_ = (int (*) ()) dlsym(zlib_library, "inflateInit2_");
- zlib_inflateEnd = (int (*) ()) dlsym(zlib_library, "inflateEnd");
- }
-#endif
-
return TRUE;
}
static gboolean plugin_unload(PurplePlugin *plugin)
{
-#ifdef HAVE_ZLIB
- if (zlib_library != NULL)
- {
- dlclose(zlib_library);
- zlib_library = NULL;
- zlib_inflate = NULL;
- zlib_inflateInit2_ = NULL;
- zlib_inflateEnd = NULL;
- }
-#endif
return TRUE;
}
============================================================
--- libpurple/protocols/facebook/libfacebook.h bb3ffbaac4c4edba105d1b79dd840c5744838ebf
+++ libpurple/protocols/facebook/libfacebook.h 30723f2d8d98def05c0e4811c6f95f4f5242364d
@@ -21,7 +21,7 @@
#ifndef LIBFACEBOOK_H
#define LIBFACEBOOK_H
-#define FACEBOOK_PLUGIN_VERSION "1.50"
+#define FACEBOOK_PLUGIN_VERSION "1.51"
#include <glib.h>
@@ -67,12 +67,7 @@
#include "version.h"
#ifdef HAVE_ZLIB
-/* for dynamically loading gzip uncompression */
-#include <zlib.h>
-static void *zlib_library = NULL;
-static int (*zlib_inflate)(z_streamp, int) = NULL;
-static int (*zlib_inflateEnd)(z_streamp) = NULL;
-static int (*zlib_inflateInit2_)(z_streamp, int, char *, int) = NULL;
+# include <zlib.h>
#endif
#define FB_LAST_MESSAGE_MAX 10
@@ -90,6 +85,7 @@ struct _FacebookAccount {
GSList *dns_queries;
GHashTable *cookie_table;
gchar *post_form_id;
+ guint post_form_id_refresh_timer;
gint32 uid;
guint buddy_list_timer;
guint friend_request_timer;
@@ -97,6 +93,7 @@ struct _FacebookAccount {
guint message_fetch_sequence;
gint64 last_messages[FB_LAST_MESSAGE_MAX];
guint16 next_message_pointer;
+ GSList *resending_messages;
GSList *auth_buddies;
GHashTable *hostname_ip_cache;
guint notifications_timer;
@@ -105,6 +102,8 @@ struct _FacebookAccount {
guint perpetual_messages_timer;
gchar *last_status_message;
gboolean is_idle;
+ GHashTable *sent_messages_hash;
+ gint last_inbox_count;
};
struct _FacebookBuddy {
============================================================
--- libpurple/protocols/facebook/pidgin-facebookchat.rc 18e43509de2ce09ee5a4fde52ac334a133eb2d02
+++ libpurple/protocols/facebook/pidgin-facebookchat.rc c5f4af2b3b3bf3614532c11fbff8e7d901d8b8ca
@@ -1,7 +1,7 @@ 1 VERSIONINFO
1 VERSIONINFO
-FILEVERSION 1,50,0,0
-PRODUCTVERSION 1,50,0,0
+FILEVERSION 1,51,0,0
+PRODUCTVERSION 1,51,0,0
FILEOS 0x40004 // VOS_NT_WINDOWS32
FILETYPE 0x2 // VFT_DLL
{
@@ -12,8 +12,8 @@ BLOCK "StringFileInfo"
VALUE "CompanyName", "Eion Robb\0"
VALUE "FileDescription", "Facebook Chat plugin for Pidgin\0"
VALUE "ProductName", "pidgin-facebookchat\0"
- VALUE "FileVersion", "1.50\0"
- VALUE "ProductVersion", "1.50\0"
+ VALUE "FileVersion", "1.51\0"
+ VALUE "ProductVersion", "1.51\0"
VALUE "InternalName", "pidgin-facebookchat\0"
VALUE "OriginalFilename", "pidgin-facebookchat.dll\0"
VALUE "Comments", "http://pidgin-facebookchat.googlecode.com/\0"
============================================================
--- libpurple/protocols/facebook/rss.xml d62ddd39ac317b8409bc64d84a3c848dafeb23b7
+++ libpurple/protocols/facebook/rss.xml 63a62d482440ae47b29631cfb4f7bb866184134c
@@ -13,6 +13,31 @@
<width>48</width><height>48</height>
</image>
<item>
+ <title>Version 1.51</title>
+ <link>http://code.google.com/p/pidgin-facebookchat/issues/detail?id=24#c44</link>
+ <description><![CDATA[Version 1.51 of the pidgin-facebookchat plugin is available for download.<br/>
+ <br/>
+ Download (as usual) from:<br/>
+ <a href="http://code.google.com/p/pidgin-facebookchat/downloads/list">
+ http://code.google.com/p/pidgin-facebookchat/downloads/list</a><br/>
+ <br/>
+ A list of the changes to the plugin is at:<br/>
+ <a href="http://code.google.com/p/pidgin-facebookchat/wiki/Changelog">
+ http://code.google.com/p/pidgin-facebookchat/wiki/Changelog</a><br/>
+ <br/>
+ If you're a package maintainer, you'll want to look at my blog post:<br/>
+ <a href="http://eion.robbmob.com/blog/2009/06/12/pidgin-facebookchat-v1-51-important-changes/">
+ http://eion.robbmob.com/blog/2009/06/12/pidgin-facebookchat-v1-51-important-changes/</a><br/>
+ (Shameless plug)<br/>
+ <br/>
+ And of course, if you like this plugin feel free to leave a comment or donation at<br/>
+ <a href="http://code.google.com/p/pidgin-facebookchat/wiki/Donate">
+ http://code.google.com/p/pidgin-facebookchat/wiki/Donate</a>
+ ]]></description>
+ <pubDate>Sat, 13 June 2009 19:24:19 +1200</pubDate>
+ <guid isPermaLink="true">http://code.google.com/p/pidgin-facebookchat/issues/detail?id=24#c44</guid>
+ </item>
+ <item>
<title>Version 1.50</title>
<link>http://code.google.com/p/pidgin-facebookchat/issues/detail?id=24#c42</link>
<description><![CDATA[So its been a while since the last version so I've bumped it up to v1.50.
More information about the Commits
mailing list