changeset 27760:e9cf897bd873

propagate from branch 'im.pidgin.pidgin' (head ae7f8e3acb446776f833c3b44514295ae56184b3) to branch 'im.pidgin.pidgin.yaz' (head 952151791a1f3b4e3469a5621f4cfa2de608a8b8)
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Wed, 30 Jan 2008 10:53:22 +0000
parents 54479172725e (current diff) ae5917260eac (diff)
children 329c9cc323fb
files autogen.sh libpurple/protocols/irc/irc.c libpurple/protocols/jabber/jabber.c libpurple/protocols/jabber/si.c libpurple/protocols/msn/msn.c libpurple/protocols/oscar/oscar.c libpurple/protocols/yahoo/util.c libpurple/protocols/yahoo/yahoo.c libpurple/protocols/yahoo/yahoo_filexfer.c libpurple/util.c pidgin/gtkconv.c pidgin/gtkimhtml.c pidgin/gtkimhtmltoolbar.c pidgin/gtkmain.c pidgin/gtkprefs.c pidgin/gtkutils.c pidgin/gtkutils.h
diffstat 120 files changed, 3554 insertions(+), 1220 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Thu Jan 10 05:32:41 2008 +0000
+++ b/COPYRIGHT	Wed Jan 30 10:53:22 2008 +0000
@@ -270,6 +270,7 @@
 Padraig O'Briain
 Christopher O'Brien (siege)
 Jon Oberheide
+Yusuke Odate
 Ruediger Oertel
 Gudmundur Bjarni Olafsson
 Bartosz Oler
@@ -322,6 +323,7 @@
 Jean-Francois Roy
 Peter Ruibal
 Sam S.
+Thanumalayan S.
 Pradyumna Sampath
 Arvind Samptur
 Tom Samstag
@@ -370,6 +372,7 @@
 Richard Stellingwerff
 Charlie Stockman
 David Stoddard
+Andreas Stührk
 Oleg Sukhodolsky
 Sun Microsystems
 Mårten Svantesson (fursten)
@@ -425,6 +428,7 @@
 Andrew Whewell
 Simon Wilkinson
 Dan Willemsen
+Justin Williams (Jaywalker)
 Jason Willis
 Matt Wilson
 Dan Winship
--- a/ChangeLog	Thu Jan 10 05:32:41 2008 +0000
+++ b/ChangeLog	Wed Jan 30 10:53:22 2008 +0000
@@ -9,6 +9,13 @@
 	  now required to use Bonjour.
 	* Partial support for viewing ICQ status notes (Collin from
 	  ComBOTS GmbH).
+	* Support for /notice on IRC.
+	* Support for Yahoo Messenger 7.0+ file transfer method (Thanumalayan S.)
+	* Support for retrieving full names and addresses from the address book
+	  on Yahoo! Japan (Yusuke Odate)
+	* The AIM/ICQ server-side preference for "allow others to see me
+	  as idle" is no longer unconditionally set to "yes" even when
+	  your libpurple preference is "no."
 
 	Pidgin:
 	* Added the ability to theme conversation name colors (red and blue)
@@ -22,15 +29,18 @@
 	Finch:
 	* Color is used in the buddylist to indicate status, and the conversation
 	  window to indicate various message attributes. Look at the sample gntrc
-	  file in the man-page for details.
+	  file in the man page for details.
 	* The default keybinding for dump-screen is now M-D and uses a file
 	  request dialog. M-d will properly delete-forward-word, and M-f has been
 	  fixed to imitate readline's behavior.
 	* New bindings alt+tab and alt+shift+tab to help navigating between the
-	  higlighted windows (details on the man-page).
+	  higlighted windows (details on the man page).
 	* Recently signed on (or off) buddies blink in the buddy list.
-	* New action 'Room List' in the action-list can be used to get the list of
+	* New action 'Room List' in the action list can be used to get the list of
 	  available chat rooms for an online account.
+	* The 'Grouping' plugin can be used for alternate grouping in the
+	  buddylist. The current options are 'Group Online/Offline' and 'No
+	  Group'.
 
 version 2.3.1 (12/7/2007):
 	http://developer.pidgin.im/query?status=closed&milestone=2.3.1
--- a/ChangeLog.API	Thu Jan 10 05:32:41 2008 +0000
+++ b/ChangeLog.API	Wed Jan 30 10:53:22 2008 +0000
@@ -18,6 +18,31 @@
 			* purple_roomlist_field_get_type
 			* purple_roomlist_field_get_label
 			* purple_roomlist_field_get_hidden
+		* unlocalized_name field in PurpleAttentionType for UIs that need it.
+		* Some accessor and mutator functions for PurpleAttentionType:
+			* purple_attention_type_set_name
+			* purple_attention_type_set_incoming_desc
+			* purple_attention_type_set_outgoing_desc
+			* purple_attention_type_set_icon_name
+			* purple_attention_type_set_unlocalized_name
+			* purple_attention_type_get_name
+			* purple_attention_type_get_incoming_desc
+			* purple_attention_type_get_outgoing_desc
+			* purple_attention_type_get_icon_name
+			* purple_attention_type_get_unlocalized_name
+		* Add some PurpleBuddyListNode accessor functions:
+			* purple_blist_node_get_parent
+			* purple_blist_node_get_first_child
+			* purple_blist_node_get_sibling_next
+			* purple_blist_node_get_sibling_prev
+		* Added last_received to PurpleConnection, the time_t of the
+		  last received packet.
+		* Added some more accessor functions:
+			* purple_chat_get_account
+			* purple_chat_get_components
+			* purple_connection_get_prpl
+			* purple_xfer_get_start_time
+			* purple_xfer_get_end_time
 
 	Pidgin:
 		Added:
@@ -44,6 +69,12 @@
 		* finch_roomlist_get_ui_ops and finch_roomlist_show_all
 		* finch_request_field_get_widget to get the widget for a request
 		  field.
+		* finch_blist_get_tree to get the GntTree widget representing the
+		  buddy list.
+		* FinchBlistManager structure to manage the buddylist view, and some
+		  util functions finch_blist_install_manager,
+		  finch_blist_uninstall_manager, finch_blist_manager_find and
+		  finch_blist_manager_add_node.
 
 		libgnt:
 		* Added gnt_tree_set_row_color to set the color for a row in a tree.
@@ -53,6 +84,7 @@
 		  string.
 		* Added gnt_style_get_color to get a color pair from an entry in
 		  ~/.gntrc
+		* Added gnt_tree_get_parent_key to get the key for the parent row.
 
 version 2.3.0 (11/24/2007):
 	libpurple:
--- a/autogen.sh	Thu Jan 10 05:32:41 2008 +0000
+++ b/autogen.sh	Wed Jan 30 10:53:22 2008 +0000
@@ -9,42 +9,42 @@
 	echo;
 	echo "You must have glib-gettextize installed to compile Pidgin.";
 	echo;
-	exit;
+	exit 1;
 }
 
 (intltoolize --version) < /dev/null > /dev/null 2>&1 || {
 	echo;
 	echo "You must have intltool installed to compile Pidgin.";
 	echo;
-	exit;
+	exit 1;
 }
 
 (libtoolize --version) < /dev/null > /dev/null 2>&1 || {
 	echo;
 	echo "You must have libtool installed to compile Pidgin.";
 	echo;
-	exit;
+	exit 1;
 }
 
 (automake --version) < /dev/null > /dev/null 2>&1 || {
 	echo;
 	echo "You must have automake installed to compile Pidgin.";
 	echo;
-	exit;
+	exit 1;
 }
 
 (autoconf --version) < /dev/null > /dev/null 2>&1 || {
 	echo;
 	echo "You must have autoconf installed to compile Pidgin.";
 	echo;
-	exit;
+	exit 1;
 }
 
 echo "Generating configuration files for Pidgin, please wait...."
 echo;
 
 echo "Running libtoolize, please ignore non-fatal messages...."
-echo n | libtoolize --copy --force || exit;
+echo n | libtoolize --copy --force || exit 1;
 
 # Add other directories to this list if people continue to experience
 # brokennesses ...  Obviously the real answer is for them to fix it
@@ -60,11 +60,11 @@
 libtoolize -c -f --automake
 glib-gettextize --force --copy
 intltoolize --force --copy
-aclocal $ACLOCAL_FLAGS || exit;
-autoheader || exit;
+aclocal $ACLOCAL_FLAGS || exit 1;
+autoheader || exit 1;
 automake --add-missing --copy;
-autoconf || exit;
-automake || exit;
+autoconf || exit 1;
+automake || exit 1;
 
 echo;
 echo "Running ./configure ${CONFIGURE_ARGS} $@"
--- a/finch/finch.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/finch/finch.h	Wed Jan 30 10:53:22 2008 +0000
@@ -29,3 +29,7 @@
 #define FINCH_UI "gnt-purple"
 
 #define FINCH_PREFS_ROOT "/finch"
+
+#define FINCH_GET_DATA(obj)        (obj)->ui_data
+#define FINCH_SET_DATA(obj, data)  (obj)->ui_data = data
+
--- a/finch/gntaccount.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/finch/gntaccount.c	Wed Jan 30 10:53:22 2008 +0000
@@ -686,7 +686,7 @@
 	}
 
 	g_signal_connect(G_OBJECT(accounts.tree), "toggled", G_CALLBACK(account_toggled), NULL);
-	
+
 	gnt_tree_set_col_width(GNT_TREE(accounts.tree), 0, 40);
 	gnt_tree_set_col_width(GNT_TREE(accounts.tree), 1, 10);
 	gnt_box_add_widget(GNT_BOX(accounts.window), accounts.tree);
@@ -708,11 +708,11 @@
 	gnt_box_add_widget(GNT_BOX(box), button);
 	gnt_util_set_trigger_widget(GNT_WIDGET(accounts.tree), GNT_KEY_DEL, button);
 	g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(delete_account_cb), accounts.tree);
-	
+
 	gnt_box_add_widget(GNT_BOX(accounts.window), box);
 
 	g_signal_connect(G_OBJECT(accounts.window), "destroy", G_CALLBACK(reset_accounts_win), NULL);
-	
+
 	gnt_widget_show(accounts.window);
 }
 
@@ -981,7 +981,7 @@
 
 		gnt_box_add_widget(GNT_BOX(uihandle), gnt_hline_new());
 
-		widget = finch_retrieve_user_info(account->gc, remote_user);
+		widget = finch_retrieve_user_info(purple_account_get_connection(account), remote_user);
 		for (iter = GNT_BOX(widget)->list; iter; iter = iter->next) {
 			if (GNT_IS_BUTTON(iter->data)) {
 				gnt_widget_destroy(iter->data);
--- a/finch/gntblist.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/finch/gntblist.c	Wed Jan 30 10:53:22 2008 +0000
@@ -81,6 +81,9 @@
 	/* These are the menuitems that get regenerated */
 	GntMenuItem *accounts;
 	GntMenuItem *plugins;
+	GntMenuItem *grouping;
+
+	FinchBlistManager *manager;
 } FinchBlist;
 
 typedef struct
@@ -115,7 +118,12 @@
 static void add_chat(PurpleChat *chat, FinchBlist *ggblist);
 static void add_node(PurpleBlistNode *node, FinchBlist *ggblist);
 static void node_update(PurpleBuddyList *list, PurpleBlistNode *node);
+#if 0
+static gboolean is_contact_online(PurpleContact *contact);
+static gboolean is_group_online(PurpleGroup *group);
+#endif
 static void draw_tooltip(FinchBlist *ggblist);
+static void tooltip_for_buddy(PurpleBuddy *buddy, GString *str, gboolean full);
 static gboolean remove_typing_cb(gpointer null);
 static void remove_peripherals(FinchBlist *ggblist);
 static const char * get_display_name(PurpleBlistNode *node);
@@ -125,6 +133,7 @@
 static void update_buddy_display(PurpleBuddy *buddy, FinchBlist *ggblist);
 static void account_signed_on_cb(PurpleConnection *pc, gpointer null);
 static void finch_request_add_buddy(PurpleAccount *account, const char *username, const char *grp, const char *alias);
+static void menu_group_set_cb(GntMenuItem *item, gpointer null);
 
 /* Sort functions */
 static int blist_node_compare_position(PurpleBlistNode *n1, PurpleBlistNode *n2);
@@ -137,14 +146,178 @@
 static int color_offline;
 static int color_idle;
 
+/**
+ * Buddy List Manager functions.
+ */
+
+static gboolean default_can_add_node(PurpleBlistNode *node)
+{
+	gboolean offline = purple_prefs_get_bool(PREF_ROOT "/showoffline");
+
+	if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		PurpleBuddy *buddy = (PurpleBuddy*)node;
+		FinchBlistNode *fnode = FINCH_GET_DATA(node);
+		if (!purple_buddy_get_contact(buddy))
+			return FALSE; /* When a new buddy is added and show-offline is set */
+		if (PURPLE_BUDDY_IS_ONLINE(buddy))
+			return TRUE;  /* The buddy is online */
+		if (!purple_account_is_connected(purple_buddy_get_account(buddy)))
+			return FALSE; /* The account is disconnected. Do not show */
+		if (offline)
+			return TRUE;  /* We want to see offline buddies too */
+		if (fnode && fnode->signed_timer)
+			return TRUE;  /* Show if the buddy just signed off */
+		if (purple_blist_node_get_bool(node, "show_offline"))
+			return TRUE;
+	} else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
+		PurpleBlistNode *nd;
+		for (nd = purple_blist_node_get_first_child(node);
+				nd; nd = purple_blist_node_get_sibling_next(nd)) {
+			if (default_can_add_node(nd))
+				return TRUE;
+		}
+	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
+		PurpleChat *chat = (PurpleChat*)node;
+		if (purple_account_is_connected(purple_chat_get_account(chat)))
+			return TRUE;  /* Show whenever the account is online */
+	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
+		PurpleBlistNode *nd;
+		gboolean empty = purple_prefs_get_bool(PREF_ROOT "/emptygroups");
+		if (empty)
+			return TRUE;  /* If we want to see empty groups, we can show any group */
+
+		for (nd = purple_blist_node_get_first_child(node);
+				nd; nd = purple_blist_node_get_sibling_next(nd)) {
+			if (default_can_add_node(nd))
+				return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+static gpointer default_find_parent(PurpleBlistNode *node)
+{
+	gpointer ret = NULL;
+	switch (purple_blist_node_get_type(node)) {
+		case PURPLE_BLIST_BUDDY_NODE:
+		case PURPLE_BLIST_CONTACT_NODE:
+		case PURPLE_BLIST_CHAT_NODE:
+			ret = purple_blist_node_get_parent(node);
+			break;
+		default:
+			break;
+	}
+	if (ret)
+		add_node(ret, ggblist);
+	return ret;
+}
+
+static gboolean default_create_tooltip(gpointer selected_row, GString **body, char **tool_title)
+{
+	GString *str;
+	PurpleBlistNode *node = selected_row;
+	int lastseen = 0;
+	char *title;
+
+	if (!node ||
+			purple_blist_node_get_type(node) == PURPLE_BLIST_OTHER_NODE)
+		return FALSE;
+
+	str = g_string_new("");
+
+	if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
+		PurpleBuddy *pr = purple_contact_get_priority_buddy((PurpleContact*)node);
+		gboolean offline = !PURPLE_BUDDY_IS_ONLINE(pr);
+		gboolean showoffline = purple_prefs_get_bool(PREF_ROOT "/showoffline");
+		const char *name = purple_buddy_get_name(pr);
+
+		title = g_strdup(name);
+		tooltip_for_buddy(pr, str, TRUE);
+		for (node = purple_blist_node_get_first_child(node); node; node = purple_blist_node_get_sibling_next(node)) {
+			PurpleBuddy *buddy = (PurpleBuddy*)node;
+			if (offline) {
+				int value = purple_blist_node_get_int(node, "last_seen");
+				if (value > lastseen)
+					lastseen = value;
+			}
+			if (node == (PurpleBlistNode*)pr)
+				continue;
+			if (!purple_account_is_connected(purple_buddy_get_account(buddy)))
+				continue;
+			if (!showoffline && !PURPLE_BUDDY_IS_ONLINE(buddy))
+				continue;
+			str = g_string_append(str, "\n----------\n");
+			tooltip_for_buddy(buddy, str, FALSE);
+		}
+	} else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		PurpleBuddy *buddy = (PurpleBuddy *)node;
+		tooltip_for_buddy(buddy, str, TRUE);
+		title = g_strdup(purple_buddy_get_name(buddy));
+		if (!PURPLE_BUDDY_IS_ONLINE((PurpleBuddy*)node))
+			lastseen = purple_blist_node_get_int(node, "last_seen");
+	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
+		PurpleGroup *group = (PurpleGroup *)node;
+
+		g_string_append_printf(str, _("Online: %d\nTotal: %d"),
+						purple_blist_get_group_online_count(group),
+						purple_blist_get_group_size(group, FALSE));
+
+		title = g_strdup(purple_group_get_name(group));
+	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
+		PurpleChat *chat = (PurpleChat *)node;
+		PurpleAccount *account = purple_chat_get_account(chat);
+
+		g_string_append_printf(str, _("Account: %s (%s)"),
+				purple_account_get_username(account),
+				purple_account_get_protocol_name(account));
+
+		title = g_strdup(purple_chat_get_name(chat));
+	} else {
+		g_string_free(str, TRUE);
+		return FALSE;
+	}
+
+	if (lastseen > 0) {
+		char *tmp = purple_str_seconds_to_string(time(NULL) - lastseen);
+		g_string_append_printf(str, _("\nLast Seen: %s ago"), tmp);
+		g_free(tmp);
+	}
+
+	if (tool_title)
+		*tool_title = title;
+	else
+		g_free(title);
+
+	if (body)
+		*body = str;
+	else
+		g_string_free(str, TRUE);
+
+	return TRUE;
+}
+
+static FinchBlistManager default_manager =
+{
+	"default",
+	N_("Default"),
+	NULL,
+	NULL,
+	default_can_add_node,
+	default_find_parent,
+	default_create_tooltip,
+	{NULL, NULL, NULL, NULL}
+};
+static GList *managers;
+
 static FinchBlistNode *
 create_finch_blist_node(PurpleBlistNode *node, gpointer row)
 {
-	FinchBlistNode *fnode = node->ui_data;
+	FinchBlistNode *fnode = FINCH_GET_DATA(node);
 	if (!fnode) {
 		fnode = g_new0(FinchBlistNode, 1);
 		fnode->signed_timer = 0;
-		node->ui_data = fnode;
+		FINCH_SET_DATA(node, fnode);
 	}
 	fnode->row = row;
 	return fnode;
@@ -153,13 +326,13 @@
 static void
 reset_blist_node_ui_data(PurpleBlistNode *node)
 {
-	FinchBlistNode *fnode = node->ui_data;
+	FinchBlistNode *fnode = FINCH_GET_DATA(node);
 	if (fnode == NULL)
 		return;
 	if (fnode->signed_timer)
 		purple_timeout_remove(fnode->signed_timer);
 	g_free(fnode);
-	node->ui_data = NULL;
+	FINCH_SET_DATA(node, NULL);
 }
 
 static int
@@ -192,7 +365,7 @@
 get_blist_node_flag(PurpleBlistNode *node)
 {
 	GntTextFormatFlags flag = 0;
-	FinchBlistNode *fnode = node->ui_data;
+	FinchBlistNode *fnode = FINCH_GET_DATA(node);
 
 	if (ggblist->tagged && g_list_find(ggblist->tagged, node))
 		flag |= GNT_TEXT_FLAG_BOLD;
@@ -201,7 +374,7 @@
 		flag |= GNT_TEXT_FLAG_BLINK;
 	else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
 		node = (PurpleBlistNode*)purple_contact_get_priority_buddy((PurpleContact *)node);
-		fnode = node->ui_data;
+		fnode = FINCH_GET_DATA(node);
 		if (fnode && fnode->signed_timer)
 			flag |= GNT_TEXT_FLAG_BLINK;
 	}
@@ -216,12 +389,16 @@
 	gnt_tree_set_row_color(GNT_TREE(ggblist->tree), node, get_display_color(node));
 }
 
+#if 0
 static gboolean
 is_contact_online(PurpleContact *contact)
 {
 	PurpleBlistNode *node;
-	for (node = ((PurpleBlistNode*)contact)->child; node; node = node->next) {
-		if (PURPLE_BUDDY_IS_ONLINE((PurpleBuddy*)node))
+	for (node = purple_blist_node_get_first_child(((PurpleBlistNode*)contact)); node;
+			node = purple_blist_node_get_sibling_next(node)) {
+		FinchBlistNode *fnode = FINCH_GET_DATA(node);
+		if (PURPLE_BUDDY_IS_ONLINE((PurpleBuddy*)node) ||
+				(fnode && fnode->signed_timer))
 			return TRUE;
 	}
 	return FALSE;
@@ -231,7 +408,8 @@
 is_group_online(PurpleGroup *group)
 {
 	PurpleBlistNode *node;
-	for (node = ((PurpleBlistNode*)group)->child; node; node = node->next) {
+	for (node = purple_blist_node_get_first_child(((PurpleBlistNode*)group)); node;
+			node = purple_blist_node_get_sibling_next(node)) {
 		if (PURPLE_BLIST_NODE_IS_CHAT(node) &&
 				purple_account_is_connected(((PurpleChat *)node)->account))
 			return TRUE;
@@ -240,14 +418,22 @@
 	}
 	return FALSE;
 }
+#endif
 
 static void
 new_node(PurpleBlistNode *node)
 {
 }
 
-static void add_node(PurpleBlistNode *node, FinchBlist *ggblist)
+static void
+add_node(PurpleBlistNode *node, FinchBlist *ggblist)
 {
+	if (FINCH_GET_DATA(node))
+		return;
+
+	if (!ggblist->manager->can_add_node(node))
+		return;
+
 	if (PURPLE_BLIST_NODE_IS_BUDDY(node))
 		add_buddy((PurpleBuddy*)node, ggblist);
 	else if (PURPLE_BLIST_NODE_IS_CONTACT(node))
@@ -256,9 +442,15 @@
 		add_group((PurpleGroup*)node, ggblist);
 	else if (PURPLE_BLIST_NODE_IS_CHAT(node))
 		add_chat((PurpleChat *)node, ggblist);
+
 	draw_tooltip(ggblist);
 }
 
+void finch_blist_manager_add_node(PurpleBlistNode *node)
+{
+	add_node(node, ggblist);
+}
+
 static void
 remove_tooltip(FinchBlist *ggblist)
 {
@@ -270,9 +462,10 @@
 static void
 node_remove(PurpleBuddyList *list, PurpleBlistNode *node)
 {
-	FinchBlist *ggblist = list->ui_data;
-
-	if (ggblist == NULL || node->ui_data == NULL)
+	FinchBlist *ggblist = FINCH_GET_DATA(list);
+	PurpleBlistNode *parent;
+
+	if (ggblist == NULL || FINCH_GET_DATA(node)== NULL)
 		return;
 
 	gnt_tree_remove(GNT_TREE(ggblist->tree), node);
@@ -280,23 +473,16 @@
 	if (ggblist->tagged)
 		ggblist->tagged = g_list_remove(ggblist->tagged, node);
 
-	if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
-		PurpleContact *contact = (PurpleContact*)node->parent;
-		if ((!purple_prefs_get_bool(PREF_ROOT "/showoffline") && !is_contact_online(contact)) ||
-				contact->currentsize < 1)
-			node_remove(list, (PurpleBlistNode*)contact);
+	parent = purple_blist_node_get_parent(node);
+	for (node = purple_blist_node_get_first_child(node); node;
+			node = purple_blist_node_get_sibling_next(node))
+		node_remove(list, node);
+
+	if (parent) {
+		if (!ggblist->manager->can_add_node(parent))
+			node_remove(list, parent);
 		else
-			node_update(list, (PurpleBlistNode*)contact);
-	} else if (!PURPLE_BLIST_NODE_IS_GROUP(node)) {
-		PurpleGroup *group = (PurpleGroup*)node->parent;
-		if ((group->currentsize < 1 && !purple_prefs_get_bool(PREF_ROOT "/emptygroups")) ||
-				(!purple_prefs_get_bool(PREF_ROOT "/showoffline") && !is_group_online(group)))
-			node_remove(list, node->parent);
-		for (node = node->child; node; node = node->next)
-			reset_blist_node_ui_data(node);
-	} else {
-		for (node = node->child; node; node = node->next)
-			node_remove(list, node);
+			node_update(list, parent);
 	}
 
 	draw_tooltip(ggblist);
@@ -310,48 +496,39 @@
            happens, so maybe someone will figure it out. */
 	g_return_if_fail(node != NULL);
 
-	if (list->ui_data == NULL)
+	if (FINCH_GET_DATA(list)== NULL)
 		return;   /* XXX: this is probably the place to auto-join chats */
 
 	if (ggblist->window == NULL)
 		return;
 
-	if (node->ui_data != NULL) {
+	if (FINCH_GET_DATA(node)!= NULL) {
 		gnt_tree_change_text(GNT_TREE(ggblist->tree), node,
 				0, get_display_name(node));
 		gnt_tree_sort_row(GNT_TREE(ggblist->tree), node);
 		blist_update_row_flags(node);
+		if (gnt_tree_get_parent_key(GNT_TREE(ggblist->tree), node) !=
+				ggblist->manager->find_parent(node))
+			node_remove(list, node);
 	}
 
 	if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
 		PurpleBuddy *buddy = (PurpleBuddy*)node;
-		if (purple_account_is_connected(buddy->account) &&
-				(PURPLE_BUDDY_IS_ONLINE(buddy) || purple_prefs_get_bool(PREF_ROOT "/showoffline")))
-			add_node((PurpleBlistNode*)buddy, list->ui_data);
-
-		node_update(list, node->parent);
+		add_node((PurpleBlistNode*)buddy, FINCH_GET_DATA(list));
+		node_update(list, purple_blist_node_get_parent(node));
 	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
-		add_chat((PurpleChat *)node, list->ui_data);
+		add_node(node, FINCH_GET_DATA(list));
 	} else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
-		PurpleContact *contact = (PurpleContact*)node;
-		if ((!purple_prefs_get_bool(PREF_ROOT "/showoffline") && !is_contact_online(contact)) ||
-				contact->currentsize < 1)
-			/* nothing */;
-		else {
-			if (node->ui_data == NULL) {
-				/* The core seems to expect the UI to add the buddies. */
-				for (node = node->child; node; node = node->next)
-					add_node(node, list->ui_data);
-			}
+		if (FINCH_GET_DATA(node)== NULL) {
+			/* The core seems to expect the UI to add the buddies. */
+			for (node = purple_blist_node_get_first_child(node); node; node = purple_blist_node_get_sibling_next(node))
+				add_node(node, FINCH_GET_DATA(list));
 		}
 	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
-		PurpleGroup *group = (PurpleGroup*)node;
-		if (!purple_prefs_get_bool(PREF_ROOT "/emptygroups") &&
-				((!purple_prefs_get_bool(PREF_ROOT "/showoffline") && !is_group_online(group)) ||
-				 group->currentsize < 1))
+		if (!ggblist->manager->can_add_node(node))
 			node_remove(list, node);
 		else
-			add_node(node, list->ui_data);
+			add_node(node, FINCH_GET_DATA(list));
 	}
 }
 
@@ -362,7 +539,10 @@
 		return;
 
 	ggblist = g_new0(FinchBlist, 1);
-	list->ui_data = ggblist;
+	FINCH_SET_DATA(list, ggblist);
+	ggblist->manager = finch_blist_manager_find(purple_prefs_get_string(PREF_ROOT "/grouping"));
+	if (!ggblist->manager)
+		ggblist->manager = &default_manager;
 }
 
 static void
@@ -399,6 +579,8 @@
 		purple_blist_add_group(grp, NULL);
 	}
 
+	/* XXX: Ask if there's already the same buddy in the same group (#4553) */
+
 	buddy = purple_buddy_new(account, username, alias);
 	purple_blist_add_buddy(buddy, NULL, grp, NULL);
 	purple_account_add_buddy(account, buddy);
@@ -447,6 +629,7 @@
 	GHashTable *hash = NULL;
 	PurpleConnection *gc;
 	gboolean autojoin;
+	PurplePluginProtocolInfo *info;
 
 	account = purple_request_fields_get_account(allfields, "account");
 	name = purple_request_fields_get_string(allfields, "name");
@@ -456,15 +639,15 @@
 
 	if (!purple_account_is_connected(account) || !name || !*name)
 		return;
-	
+
 	if (!group || !*group)
 		group = _("Chats");
 
 	gc = purple_account_get_connection(account);
-
-	if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults != NULL)
-		hash = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, name);
-	
+	info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
+	if (info->chat_info_defaults != NULL)
+		hash = info->chat_info_defaults(gc, name);
+
 	chat = purple_chat_new(account, name, hash);
 
 	if (chat != NULL) {
@@ -476,7 +659,7 @@
 		purple_blist_alias_chat(chat, alias);
 		purple_blist_node_set_bool((PurpleBlistNode*)chat, "gnt-autojoin", autojoin);
 		if (autojoin)
-			serv_join_chat(chat->account->gc, chat->components);
+			serv_join_chat(purple_account_get_connection(purple_chat_get_account(chat)), purple_chat_get_components(chat));
 	}
 }
 
@@ -501,7 +684,7 @@
 	field = purple_request_field_string_new("alias", _("Alias"), alias, FALSE);
 	purple_request_field_group_add_field(group, field);
 
-	field = purple_request_field_string_new("group", _("Group"), grp ? grp->name : NULL, FALSE);
+	field = purple_request_field_string_new("group", _("Group"), grp ? purple_group_get_name(grp) : NULL, FALSE);
 	purple_request_field_group_add_field(group, field);
 
 	field = purple_request_field_bool_new("autojoin", _("Auto-join"), FALSE);
@@ -578,11 +761,14 @@
 static void
 add_group(PurpleGroup *group, FinchBlist *ggblist)
 {
+	gpointer parent;
 	PurpleBlistNode *node = (PurpleBlistNode *)group;
-	if (node->ui_data)
+	if (FINCH_GET_DATA(node))
 		return;
+	parent = ggblist->manager->find_parent((PurpleBlistNode*)group);
 	create_finch_blist_node(node, gnt_tree_add_row_after(GNT_TREE(ggblist->tree), group,
-			gnt_tree_create_row(GNT_TREE(ggblist->tree), get_display_name(node)), NULL, NULL));
+			gnt_tree_create_row(GNT_TREE(ggblist->tree), get_display_name(node)),
+			parent, NULL));
 	gnt_tree_set_expanded(GNT_TREE(ggblist->tree), node,
 		!purple_blist_node_get_bool(node, "collapsed"));
 }
@@ -638,7 +824,7 @@
 		strncpy(status, "~", sizeof(status) - 1);
 	}
 	else if (PURPLE_BLIST_NODE_IS_GROUP(node))
-		return ((PurpleGroup*)node)->name;
+		return purple_group_get_name((PurpleGroup*)node);
 
 	snprintf(text, sizeof(text) - 1, "%s %s", status, name);
 
@@ -648,41 +834,39 @@
 static void
 add_chat(PurpleChat *chat, FinchBlist *ggblist)
 {
-	PurpleGroup *group;
+	gpointer parent;
 	PurpleBlistNode *node = (PurpleBlistNode *)chat;
-	if (node->ui_data)
+	if (FINCH_GET_DATA(node))
 		return;
-	if (!purple_account_is_connected(chat->account))
+	if (!purple_account_is_connected(purple_chat_get_account(chat)))
 		return;
 
-	group = purple_chat_get_group(chat);
-	add_node((PurpleBlistNode*)group, ggblist);
+	parent = ggblist->manager->find_parent((PurpleBlistNode*)chat);
 
 	create_finch_blist_node(node, gnt_tree_add_row_after(GNT_TREE(ggblist->tree), chat,
 				gnt_tree_create_row(GNT_TREE(ggblist->tree), get_display_name(node)),
-				group, NULL));
+				parent, NULL));
 }
 
 static void
 add_contact(PurpleContact *contact, FinchBlist *ggblist)
 {
-	PurpleGroup *group;
+	gpointer parent;
 	PurpleBlistNode *node = (PurpleBlistNode*)contact;
 	const char *name;
 
-	if (node->ui_data)
+	if (FINCH_GET_DATA(node))
 		return;
 
 	name = get_display_name(node);
 	if (name == NULL)
 		return;
 
-	group = (PurpleGroup*)node->parent;
-	add_node((PurpleBlistNode*)group, ggblist);
+	parent = ggblist->manager->find_parent((PurpleBlistNode*)contact);
 
 	create_finch_blist_node(node, gnt_tree_add_row_after(GNT_TREE(ggblist->tree), contact,
 				gnt_tree_create_row(GNT_TREE(ggblist->tree), name),
-				group, NULL));
+				parent, NULL));
 
 	gnt_tree_set_expanded(GNT_TREE(ggblist->tree), contact, FALSE);
 }
@@ -690,23 +874,19 @@
 static void
 add_buddy(PurpleBuddy *buddy, FinchBlist *ggblist)
 {
-	PurpleContact *contact;
+	gpointer parent;
 	PurpleBlistNode *node = (PurpleBlistNode *)buddy;
-
-	if (node->ui_data)
+	PurpleContact *contact;
+
+	if (FINCH_GET_DATA(node))
 		return;
 
-	if (!purple_account_is_connected(buddy->account))
-		return;
-
-	contact = (PurpleContact*)node->parent;
-	if (!contact)   /* When a new buddy is added and show-offline is set */
-		return;
-	add_node((PurpleBlistNode*)contact, ggblist);
+	contact = purple_buddy_get_contact(buddy);
+	parent = ggblist->manager->find_parent((PurpleBlistNode*)buddy);
 
 	create_finch_blist_node(node, gnt_tree_add_row_after(GNT_TREE(ggblist->tree), buddy,
 				gnt_tree_create_row(GNT_TREE(ggblist->tree), get_display_name(node)),
-				contact, NULL));
+				parent, NULL));
 
 	blist_update_row_flags((PurpleBlistNode*)buddy);
 	if (buddy == purple_contact_get_priority_buddy(contact))
@@ -756,7 +936,7 @@
 						purple_buddy_get_account(buddy),
 						purple_buddy_get_name(buddy));
 		} else {
-			FinchConv *ggconv = conv->ui_data;
+			FinchConv *ggconv = FINCH_GET_DATA(conv);
 			gnt_window_present(ggconv->window);
 		}
 		finch_conversation_set_active(conv);
@@ -764,7 +944,7 @@
 	else if (PURPLE_BLIST_NODE_IS_CHAT(node))
 	{
 		PurpleChat *chat = (PurpleChat*)node;
-		serv_join_chat(chat->account->gc, chat->components);
+		serv_join_chat(purple_account_get_connection(purple_chat_get_account(chat)), purple_chat_get_components(chat));
 	}
 }
 
@@ -809,7 +989,7 @@
 append_proto_menu(GntMenu *menu, PurpleConnection *gc, PurpleBlistNode *node)
 {
 	GList *list;
-	PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
+	PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
 
 	if(!prpl_info || !prpl_info->blist_node_menu)
 		return;
@@ -851,7 +1031,7 @@
 			else
 				val = g_strdup(purple_request_field_string_get_value(field));
 
-			g_hash_table_replace(chat->components, g_strdup(id), val);  /* val should not be free'd */
+			g_hash_table_replace(purple_chat_get_components(chat), g_strdup(id), val);  /* val should not be free'd */
 		}
 	}
 }
@@ -864,22 +1044,24 @@
 	PurpleRequestField *field;
 	GList *parts, *iter;
 	struct proto_chat_entry *pce;
+	PurpleConnection *gc;
 
 	purple_request_fields_add_group(fields, group);
 
-	parts = PURPLE_PLUGIN_PROTOCOL_INFO(chat->account->gc->prpl)->chat_info(chat->account->gc);
+	gc = purple_account_get_connection(purple_chat_get_account(chat));
+	parts = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc))->chat_info(gc);
 
 	for (iter = parts; iter; iter = iter->next) {
 		pce = iter->data;
 		if (pce->is_int) {
 			int val;
-			const char *str = g_hash_table_lookup(chat->components, pce->identifier);
+			const char *str = g_hash_table_lookup(purple_chat_get_components(chat), pce->identifier);
 			if (!str || sscanf(str, "%d", &val) != 1)
 				val = pce->min;
 			field = purple_request_field_int_new(pce->identifier, pce->label, val);
 		} else {
 			field = purple_request_field_string_new(pce->identifier, pce->label,
-					g_hash_table_lookup(chat->components, pce->identifier), FALSE);
+					g_hash_table_lookup(purple_chat_get_components(chat), pce->identifier), FALSE);
 		}
 
 		purple_request_field_group_add_field(group, field);
@@ -920,7 +1102,7 @@
 static void
 finch_add_buddy(PurpleBlistNode *selected, PurpleGroup *grp)
 {
-	purple_blist_request_add_buddy(NULL, NULL, grp ? grp->name : NULL, NULL);
+	purple_blist_request_add_buddy(NULL, NULL, grp ? purple_group_get_name(grp) : NULL, NULL);
 }
 
 static void
@@ -961,13 +1143,13 @@
 static void
 finch_blist_get_buddy_info_cb(PurpleBlistNode *selected, PurpleBuddy *buddy)
 {
-	finch_retrieve_user_info(buddy->account->gc, purple_buddy_get_name(buddy));
+	finch_retrieve_user_info(purple_account_get_connection(purple_buddy_get_account(buddy)), purple_buddy_get_name(buddy));
 }
 
 static void
 finch_blist_menu_send_file_cb(PurpleBlistNode *selected, PurpleBuddy *buddy)
 {
-	serv_send_file(buddy->account->gc, buddy->name, NULL);
+	serv_send_file(purple_account_get_connection(purple_buddy_get_account(buddy)), purple_buddy_get_name(buddy), NULL);
 }
 
 static void
@@ -978,7 +1160,7 @@
 		b = purple_contact_get_priority_buddy((PurpleContact *)node);
 	else
 		b = (PurpleBuddy *)node;
-	finch_pounce_editor_show(b->account, b->name, NULL);
+	finch_pounce_editor_show(purple_buddy_get_account(b), purple_buddy_get_name(b), NULL);
 }
 
 
@@ -986,8 +1168,9 @@
 create_buddy_menu(GntMenu *menu, PurpleBuddy *buddy)
 {
 	PurplePluginProtocolInfo *prpl_info;
-
-	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(buddy->account->gc->prpl);
+	PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy));
+
+	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
 	if (prpl_info && prpl_info->get_info)
 	{
 		add_custom_action(menu, _("Get Info"),
@@ -1000,7 +1183,7 @@
 	if (prpl_info && prpl_info->send_file)
 	{
 		if (!prpl_info->can_receive_file ||
-			prpl_info->can_receive_file(buddy->account->gc, buddy->name))
+			prpl_info->can_receive_file(gc, purple_buddy_get_name(buddy)))
 			add_custom_action(menu, _("Send File"),
 					PURPLE_CALLBACK(finch_blist_menu_send_file_cb), buddy);
 	}
@@ -1035,11 +1218,12 @@
 	PurpleGroup *group;
 
 	cnode = (PurpleBlistNode *)contact;
-	group = (PurpleGroup*)cnode->parent;
-	for (bnode = cnode->child; bnode; bnode = bnode->next) {
+	group = (PurpleGroup*)purple_blist_node_get_parent(cnode);
+	for (bnode = purple_blist_node_get_first_child(cnode); bnode; bnode = purple_blist_node_get_sibling_next(bnode)) {
 		PurpleBuddy *buddy = (PurpleBuddy*)bnode;
-		if (purple_account_is_connected(buddy->account))
-			purple_account_remove_buddy(buddy->account, buddy, group);
+		PurpleAccount *account = purple_buddy_get_account(buddy);
+		if (purple_account_is_connected(account))
+			purple_account_remove_buddy(account, buddy, group);
 	}
 	purple_blist_remove_contact(contact);
 }
@@ -1082,7 +1266,7 @@
 	else if (PURPLE_BLIST_NODE_IS_CHAT(node))
 		name = purple_chat_get_name((PurpleChat*)node);
 	else if (PURPLE_BLIST_NODE_IS_GROUP(node))
-		name = ((PurpleGroup*)node)->name;
+		name = purple_group_get_name((PurpleGroup*)node);
 	else
 		g_return_if_reached();
 
@@ -1104,32 +1288,34 @@
 {
 	PurpleBlistNode *cnode, *bnode;
 
-	cnode = ((PurpleBlistNode*)group)->child;
+	cnode = purple_blist_node_get_first_child(((PurpleBlistNode*)group));
 
 	while (cnode) {
 		if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
-			bnode = cnode->child;
-			cnode = cnode->next;
+			bnode = purple_blist_node_get_first_child(cnode);
+			cnode = purple_blist_node_get_sibling_next(cnode);
 			while (bnode) {
 				PurpleBuddy *buddy;
 				if (PURPLE_BLIST_NODE_IS_BUDDY(bnode)) {
+					PurpleAccount *account;
 					buddy = (PurpleBuddy*)bnode;
-					bnode = bnode->next;
-					if (purple_account_is_connected(buddy->account)) {
-						purple_account_remove_buddy(buddy->account, buddy, group);
+					bnode = purple_blist_node_get_sibling_next(bnode);
+					account = purple_buddy_get_account(buddy);
+					if (purple_account_is_connected(account)) {
+						purple_account_remove_buddy(account, buddy, group);
 						purple_blist_remove_buddy(buddy);
 					}
 				} else {
-					bnode = bnode->next;
+					bnode = purple_blist_node_get_sibling_next(bnode);
 				}
 			}
 		} else if (PURPLE_BLIST_NODE_IS_CHAT(cnode)) {
 			PurpleChat *chat = (PurpleChat *)cnode;
-			cnode = cnode->next;
-			if (purple_account_is_connected(chat->account))
+			cnode = purple_blist_node_get_sibling_next(cnode);
+			if (purple_account_is_connected(purple_chat_get_account(chat)))
 				purple_blist_remove_chat(chat);
 		} else {
-			cnode = cnode->next;
+			cnode = purple_blist_node_get_sibling_next(cnode);
 		}
 	}
 
@@ -1172,7 +1358,7 @@
 	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
 		name = purple_chat_get_name((PurpleChat*)node);
 	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
-		name = ((PurpleGroup*)node)->name;
+		name = purple_group_get_name((PurpleGroup*)node);
 		sec = _("Removing this group will also remove all the buddies in the group");
 	}
 	else
@@ -1216,18 +1402,19 @@
 	PurpleGroup *tg = NULL;
 	PurpleContact *tc = NULL;
 
-	if (target == NULL)
+	if (target == NULL ||
+			purple_blist_node_get_type(target) == PURPLE_BLIST_OTHER_NODE)
 		return;
 
 	if (PURPLE_BLIST_NODE_IS_GROUP(target))
 		tg = (PurpleGroup*)target;
 	else if (PURPLE_BLIST_NODE_IS_BUDDY(target)) {
-		tc = (PurpleContact*)target->parent;
-		tg = (PurpleGroup*)target->parent->parent;
+		tc = (PurpleContact*)purple_blist_node_get_parent(target);
+		tg = (PurpleGroup*)purple_blist_node_get_parent((PurpleBlistNode*)tc);
 	} else {
 		if (PURPLE_BLIST_NODE_IS_CONTACT(target))
 			tc = (PurpleContact*)target;
-		tg = (PurpleGroup*)target->parent;
+		tg = (PurpleGroup*)purple_blist_node_get_parent(target);
 	}
 
 	if (ggblist->tagged) {
@@ -1300,6 +1487,8 @@
 	tree = GNT_TREE(ggblist->tree);
 
 	node = gnt_tree_get_selection_data(tree);
+	if (node && purple_blist_node_get_type(node) == PURPLE_BLIST_OTHER_NODE)
+		return;
 
 	if (ggblist->tooltip)
 		remove_tooltip(ggblist);
@@ -1328,7 +1517,7 @@
 	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
 		PurpleGroup *group = (PurpleGroup *)node;
 		create_group_menu(GNT_MENU(context), group);
-		title = g_strdup(group->name);
+		title = g_strdup(purple_group_get_name(group));
 	}
 
 	append_extended_menu(GNT_MENU(context), node);
@@ -1393,7 +1582,7 @@
 			purple_account_get_protocol_name(account));
 	purple_notify_user_info_add_pair(user_info, _("Account"), tmp);
 	g_free(tmp);
-	
+
 	prpl = purple_find_prpl(purple_account_get_protocol_id(account));
 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
 	if (prpl_info && prpl_info->tooltip_text) {
@@ -1442,16 +1631,15 @@
 {
 	PurpleBlistNode *node;
 	int x, y, top, width, w, h;
-	GString *str;
+	GString *str = NULL;
 	GntTree *tree;
 	GntWidget *widget, *box, *tv;
 	char *title = NULL;
-	int lastseen = 0;
 
 	widget = ggblist->tree;
 	tree = GNT_TREE(widget);
 
-	if (!gnt_widget_has_focus(ggblist->tree) || 
+	if (!gnt_widget_has_focus(ggblist->tree) ||
 			(ggblist->context && !GNT_WIDGET_IS_FLAG_SET(ggblist->context, GNT_WIDGET_INVISIBLE)))
 		return FALSE;
 
@@ -1466,65 +1654,8 @@
 	if (!node)
 		return FALSE;
 
-	str = g_string_new("");
-
-	if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
-		PurpleBuddy *pr = purple_contact_get_priority_buddy((PurpleContact*)node);
-		gboolean offline = !PURPLE_BUDDY_IS_ONLINE(pr);
-		gboolean showoffline = purple_prefs_get_bool(PREF_ROOT "/showoffline");
-		const char *name = purple_buddy_get_name(pr);
-
-		title = g_strdup(name);
-		tooltip_for_buddy(pr, str, TRUE);
-		for (node = node->child; node; node = node->next) {
-			PurpleBuddy *buddy = (PurpleBuddy*)node;
-			if (offline) {
-				int value = purple_blist_node_get_int(node, "last_seen");
-				if (value > lastseen)
-					lastseen = value;
-			}
-			if (node == (PurpleBlistNode*)pr)
-				continue;
-			if (!purple_account_is_connected(buddy->account))
-				continue;
-			if (!showoffline && !PURPLE_BUDDY_IS_ONLINE(buddy))
-				continue;
-			str = g_string_append(str, "\n----------\n");
-			tooltip_for_buddy(buddy, str, FALSE);
-		}
-	} else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
-		PurpleBuddy *buddy = (PurpleBuddy *)node;
-		tooltip_for_buddy(buddy, str, TRUE);
-		title = g_strdup(purple_buddy_get_name(buddy));
-		if (!PURPLE_BUDDY_IS_ONLINE((PurpleBuddy*)node))
-			lastseen = purple_blist_node_get_int(node, "last_seen");
-	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
-		PurpleGroup *group = (PurpleGroup *)node;
-
-		g_string_append_printf(str, _("Online: %d\nTotal: %d"),
-						purple_blist_get_group_online_count(group),
-						purple_blist_get_group_size(group, FALSE));
-
-		title = g_strdup(group->name);
-	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
-		PurpleChat *chat = (PurpleChat *)node;
-		PurpleAccount *account = chat->account;
-
-		g_string_append_printf(str, _("Account: %s (%s)"),
-				purple_account_get_username(account),
-				purple_account_get_protocol_name(account));
-
-		title = g_strdup(purple_chat_get_name(chat));
-	} else {
-		g_string_free(str, TRUE);
+	if (!ggblist->manager->create_tooltip(node, &str, &title))
 		return FALSE;
-	}
-
-	if (lastseen > 0) {
-		char *tmp = purple_str_seconds_to_string(time(NULL) - lastseen);
-		g_string_append_printf(str, _("\nLast Seen: %s ago"), tmp);
-		g_free(tmp);
-	}
 
 	gnt_widget_get_position(widget, &x, &y);
 	gnt_widget_get_size(widget, &width, NULL);
@@ -1684,7 +1815,7 @@
 {
 	PurpleBlistNode *node;
 	purple_signals_disconnect_by_handle(finch_blist_get_handle());
-	purple_get_blist()->ui_data = NULL;
+	FINCH_SET_DATA(purple_get_blist(), NULL);
 
 	node = purple_blist_get_root();
 	while (node) {
@@ -1707,6 +1838,9 @@
 	PurpleBlistNode *node;
 	PurpleBuddyList *list;
 
+	if (ggblist->manager->init)
+		ggblist->manager->init();
+
 	if (strcmp(purple_prefs_get_string(PREF_ROOT "/sort_type"), "text") == 0) {
 		gnt_tree_set_compare_func(GNT_TREE(ggblist->tree),
 			(GCompareFunc)blist_node_compare_text);
@@ -1794,11 +1928,33 @@
 redraw_blist(const char *name, PurplePrefType type, gconstpointer val, gpointer data)
 {
 	PurpleBlistNode *node, *sel;
-	if (ggblist == NULL || ggblist->window == NULL)
+	FinchBlistManager *manager;
+
+	if (ggblist == NULL)
+		return;
+
+	manager = finch_blist_manager_find(purple_prefs_get_string(PREF_ROOT "/grouping"));
+	if (manager == NULL)
+		manager = &default_manager;
+	if (ggblist->manager != manager) {
+		if (ggblist->manager->uninit)
+			ggblist->manager->uninit();
+
+		ggblist->manager = manager;
+		if (manager->can_add_node == NULL)
+			manager->can_add_node = default_can_add_node;
+		if (manager->find_parent == NULL)
+			manager->find_parent = default_find_parent;
+		if (manager->create_tooltip == NULL)
+			manager->create_tooltip = default_create_tooltip;
+	}
+
+	if (ggblist->window == NULL)
 		return;
 
 	sel = gnt_tree_get_selection_data(GNT_TREE(ggblist->tree));
 	gnt_tree_remove_all(GNT_TREE(ggblist->tree));
+
 	node = purple_blist_get_root();
 	for (; node; node = purple_blist_node_next(node, TRUE))
 		reset_blist_node_ui_data(node);
@@ -1833,6 +1989,7 @@
 	purple_prefs_add_bool(PREF_ROOT "/showoffline", FALSE);
 	purple_prefs_add_bool(PREF_ROOT "/emptygroups", FALSE);
 	purple_prefs_add_string(PREF_ROOT "/sort_type", "text");
+	purple_prefs_add_string(PREF_ROOT "/grouping", "default");
 
 	purple_prefs_connect_callback(finch_blist_get_handle(),
 			PREF_ROOT "/emptygroups", redraw_blist, NULL);
@@ -1840,9 +1997,14 @@
 			PREF_ROOT "/showoffline", redraw_blist, NULL);
 	purple_prefs_connect_callback(finch_blist_get_handle(),
 			PREF_ROOT "/sort_type", redraw_blist, NULL);
+	purple_prefs_connect_callback(finch_blist_get_handle(),
+			PREF_ROOT "/grouping", redraw_blist, NULL);
 
 	purple_signal_connect(purple_connections_get_handle(), "signed-on", purple_blist_get_handle(),
 			G_CALLBACK(account_signed_on_cb), NULL);
+
+	finch_blist_install_manager(&default_manager);
+
 	return;
 }
 
@@ -2003,7 +2165,7 @@
 static int
 blist_node_compare_position(PurpleBlistNode *n1, PurpleBlistNode *n2)
 {
-	while ((n1 = n1->prev) != NULL)
+	while ((n1 = purple_blist_node_get_sibling_prev(n1)) != NULL)
 		if (n1 == n2)
 			return 1;
 	return -1;
@@ -2016,10 +2178,10 @@
 	char *us1, *us2;
 	int ret;
 
-	if (n1->type != n2->type)
+	if (purple_blist_node_get_type(n1) != purple_blist_node_get_type(n2))
 		return blist_node_compare_position(n1, n2);
 
-	switch (n1->type)
+	switch (purple_blist_node_get_type(n1))
 	{
 		case PURPLE_BLIST_CHAT_NODE:
 			s1 = purple_chat_get_name((PurpleChat*)n1);
@@ -2051,10 +2213,10 @@
 {
 	int ret;
 
-	if (n1->type != n2->type)
+	if (purple_blist_node_get_type(n1) != purple_blist_node_get_type(n2))
 		return blist_node_compare_position(n1, n2);
 
-	switch (n1->type) {
+	switch (purple_blist_node_get_type(n1)) {
 		case PURPLE_BLIST_CONTACT_NODE:
 			n1 = (PurpleBlistNode*)purple_contact_get_priority_buddy((PurpleContact*)n1);
 			n2 = (PurpleBlistNode*)purple_contact_get_priority_buddy((PurpleContact*)n2);
@@ -2082,9 +2244,10 @@
 	int log = 0;
 	PurpleBlistNode *node;
 
-	for (node = c->child; node; node = node->next) {
+	for (node = purple_blist_node_get_first_child(c); node; node = purple_blist_node_get_sibling_next(node)) {
 		PurpleBuddy *b = (PurpleBuddy*)node;
-		log += purple_log_get_total_size(PURPLE_LOG_IM, b->name, b->account);
+		log += purple_log_get_total_size(PURPLE_LOG_IM, purple_buddy_get_name(b),
+				purple_buddy_get_account(b));
 	}
 
 	return log;
@@ -2096,15 +2259,15 @@
 	int ret;
 	PurpleBuddy *b1, *b2;
 
-	if (n1->type != n2->type)
+	if (purple_blist_node_get_type(n1) != purple_blist_node_get_type(n2))
 		return blist_node_compare_position(n1, n2);
 
-	switch (n1->type) {
+	switch (purple_blist_node_get_type(n1)) {
 		case PURPLE_BLIST_BUDDY_NODE:
 			b1 = (PurpleBuddy*)n1;
 			b2 = (PurpleBuddy*)n2;
-			ret = purple_log_get_total_size(PURPLE_LOG_IM, b2->name, b2->account) - 
-					purple_log_get_total_size(PURPLE_LOG_IM, b1->name, b1->account);
+			ret = purple_log_get_total_size(PURPLE_LOG_IM, purple_buddy_get_name(b2), purple_buddy_get_account(b2)) -
+					purple_log_get_total_size(PURPLE_LOG_IM, purple_buddy_get_name(b1), purple_buddy_get_account(b1));
 			if (ret != 0)
 				return ret;
 			break;
@@ -2165,19 +2328,17 @@
 buddy_recent_signed_on_off(gpointer data)
 {
 	PurpleBlistNode *node = data;
-	FinchBlistNode *fnode = node->ui_data;
-	PurpleBuddy *buddy = (PurpleBuddy*)node;
+	FinchBlistNode *fnode = FINCH_GET_DATA(node);
 
 	purple_timeout_remove(fnode->signed_timer);
 	fnode->signed_timer = 0;
 
-	if (!purple_account_is_connected(buddy->account) ||
-			(!PURPLE_BUDDY_IS_ONLINE(buddy) && !purple_prefs_get_bool(PREF_ROOT "/showoffline"))) {
+	if (!ggblist->manager->can_add_node(node)) {
 		node_remove(purple_get_blist(), node);
 	} else {
 		update_node_display(node, ggblist);
-		if (node->parent && PURPLE_BLIST_NODE_IS_CONTACT(node->parent))
-			update_node_display(node->parent, ggblist);
+		if (purple_blist_node_get_parent(node) && PURPLE_BLIST_NODE_IS_CONTACT(purple_blist_node_get_parent(node)))
+			update_node_display(purple_blist_node_get_parent(node), ggblist);
 	}
 
 	return FALSE;
@@ -2187,7 +2348,7 @@
 buddy_signed_on_off_cb(gpointer data)
 {
 	PurpleBlistNode *node = data;
-	FinchBlistNode *fnode = node->ui_data;
+	FinchBlistNode *fnode = FINCH_GET_DATA(node);
 	if (!ggblist || !fnode)
 		return FALSE;
 
@@ -2195,8 +2356,8 @@
 		purple_timeout_remove(fnode->signed_timer);
 	fnode->signed_timer = purple_timeout_add_seconds(6, (GSourceFunc)buddy_recent_signed_on_off, data);
 	update_node_display(node, ggblist);
-	if (node->parent && PURPLE_BLIST_NODE_IS_CONTACT(node->parent))
-		update_node_display(node->parent, ggblist);
+	if (purple_blist_node_get_parent(node) && PURPLE_BLIST_NODE_IS_CONTACT(purple_blist_node_get_parent(node)))
+		update_node_display(purple_blist_node_get_parent(node), ggblist);
 	return FALSE;
 }
 
@@ -2260,10 +2421,10 @@
 		PurpleAccount *account = iter->data;
 		PurpleConnection *gc = purple_account_get_connection(account);
 		PurplePlugin *prpl;
-		
+
 		if (!gc || !PURPLE_CONNECTION_IS_CONNECTED(gc))
 			continue;
-		prpl = gc->prpl;
+		prpl = purple_connection_get_prpl(gc);
 
 		if (PURPLE_PLUGIN_HAS_ACTIONS(prpl)) {
 			item = gnt_menuitem_new(purple_account_get_username(account));
@@ -2273,6 +2434,30 @@
 	}
 }
 
+static void
+reconstruct_grouping_menu(void)
+{
+	GList *iter;
+	GntWidget *subsub;
+
+	if (!ggblist || !ggblist->grouping)
+		return;
+
+	subsub = gnt_menu_new(GNT_MENU_POPUP);
+	gnt_menuitem_set_submenu(ggblist->grouping, GNT_MENU(subsub));
+
+	for (iter = managers; iter; iter = iter->next) {
+		char menuid[128];
+		FinchBlistManager *manager = iter->data;
+		GntMenuItem *item = gnt_menuitem_new(_(manager->name));
+		snprintf(menuid, sizeof(menuid), "grouping-%s", manager->id);
+		gnt_menuitem_set_id(GNT_MENU_ITEM(item), menuid);
+		gnt_menu_add_item(GNT_MENU(subsub), item);
+		g_object_set_data_full(G_OBJECT(item), "grouping-id", g_strdup(manager->id), g_free);
+		gnt_menuitem_set_callback(item, menu_group_set_cb, NULL);
+	}
+}
+
 static gboolean
 auto_join_chats(gpointer data)
 {
@@ -2284,9 +2469,9 @@
 			node = purple_blist_node_next(node, FALSE)) {
 		if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
 			PurpleChat *chat = (PurpleChat*)node;
-			if (chat->account == account &&
+			if (purple_chat_get_account(chat) == account &&
 					purple_blist_node_get_bool(node, "gnt-autojoin"))
-				serv_join_chat(purple_account_get_connection(chat->account), chat->components);
+				serv_join_chat(purple_account_get_connection(account), purple_chat_get_components(chat));
 		}
 	}
 	return FALSE;
@@ -2372,14 +2557,15 @@
 	if (!purple_account_is_connected(account))
 		return;
 
-	gc = purple_account_get_connection(account);	
+	gc = purple_account_get_connection(account);
 	purple_conversation_new(PURPLE_CONV_TYPE_CHAT, account, name);
 	chat = purple_blist_find_chat(account, name);
 	if (chat == NULL) {
-		if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults != NULL)
-			hash = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, name);
+		PurplePluginProtocolInfo *info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
+		if (info->chat_info_defaults != NULL)
+			hash = info->chat_info_defaults(gc, name);
 	} else {
-		hash = chat->components;
+		hash = purple_chat_get_components(chat);
 	}
 	serv_join_chat(gc, hash);
 	if (chat == NULL && hash != NULL)
@@ -2439,6 +2625,13 @@
 }
 
 static void
+menu_group_set_cb(GntMenuItem *item, gpointer null)
+{
+	const char *id = g_object_get_data(G_OBJECT(item), "grouping-id");
+	purple_prefs_set_string(PREF_ROOT "/grouping", id);
+}
+
+static void
 create_menu(void)
 {
 	GntWidget *menu, *sub, *subsub;
@@ -2479,7 +2672,7 @@
 				purple_prefs_get_bool(PREF_ROOT "/emptygroups"));
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(GNT_MENU_ITEM(item), toggle_pref_cb, PREF_ROOT "/emptygroups");
-	
+
 	item = gnt_menuitem_check_new(_("Offline buddies"));
 	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "show-offline-buddies");
 	gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item),
@@ -2513,21 +2706,25 @@
 	subsub = gnt_menu_new(GNT_MENU_POPUP);
 	gnt_menuitem_set_submenu(item, GNT_MENU(subsub));
 
-	item = gnt_menuitem_new("Buddy");
+	item = gnt_menuitem_new(_("Buddy"));
 	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "add-buddy");
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(item, menu_add_buddy_cb, NULL);
 
-	item = gnt_menuitem_new("Chat");
+	item = gnt_menuitem_new(_("Chat"));
 	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "add-chat");
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(item, menu_add_chat_cb, NULL);
 
-	item = gnt_menuitem_new("Group");
+	item = gnt_menuitem_new(_("Group"));
 	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "add-group");
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(item, menu_add_group_cb, NULL);
 
+	ggblist->grouping = item = gnt_menuitem_new(_("Grouping"));
+	gnt_menu_add_item(GNT_MENU(sub), item);
+	reconstruct_grouping_menu();
+
 	reconstruct_accounts_menu();
 	gnt_menu_add_item(GNT_MENU(menu), ggblist->accounts);
 
@@ -2679,3 +2876,43 @@
 {
 	gnt_widget_set_size(ggblist->window, width, height);
 }
+
+void finch_blist_install_manager(const FinchBlistManager *manager)
+{
+	if (!g_list_find(managers, manager)) {
+		managers = g_list_append(managers, (gpointer)manager);
+		reconstruct_grouping_menu();
+		if (strcmp(manager->id, purple_prefs_get_string(PREF_ROOT "/grouping")) == 0)
+			purple_prefs_trigger_callback(PREF_ROOT "/grouping");
+	}
+}
+
+void finch_blist_uninstall_manager(const FinchBlistManager *manager)
+{
+	if (g_list_find(managers, manager)) {
+		managers = g_list_remove(managers, manager);
+		reconstruct_grouping_menu();
+		if (strcmp(manager->id, purple_prefs_get_string(PREF_ROOT "/grouping")) == 0)
+			purple_prefs_trigger_callback(PREF_ROOT "/grouping");
+	}
+}
+
+FinchBlistManager * finch_blist_manager_find(const char *id)
+{
+	GList *iter = managers;
+	if (!id)
+		return NULL;
+
+	for (; iter; iter = iter->next) {
+		FinchBlistManager *m = iter->data;
+		if (strcmp(id, m->id) == 0)
+			return m;
+	}
+	return NULL;
+}
+
+GntTree * finch_blist_get_tree(void)
+{
+	return ggblist ? GNT_TREE(ggblist->tree) : NULL;
+}
+
--- a/finch/gntblist.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/finch/gntblist.h	Wed Jan 30 10:53:22 2008 +0000
@@ -27,12 +27,25 @@
 #define _GNT_BLIST_H
 
 #include "blist.h"
+#include "gnttree.h"
 
 /**********************************************************************
  * @name GNT BuddyList API
  **********************************************************************/
 /*@{*/
 
+typedef struct
+{
+	const char *id;                                    /**< An identifier for the manager. */
+	const char *name;                                  /**< Displayable name for the manager. */
+	gboolean (*init)(void);                            /**< Called right before it's being used. */
+	gboolean (*uninit)(void);                          /**< Called right after it's not being used any more. */
+	gboolean (*can_add_node)(PurpleBlistNode *node);   /**< Whether a node should be added to the view. */
+	gpointer (*find_parent)(PurpleBlistNode *node);    /**< Find the parent row for a node. */
+	gboolean (*create_tooltip)(gpointer selected_row, GString **body, char **title);  /**< Create tooltip for a selected row. */
+	gpointer reserved[4];
+} FinchBlistManager;
+
 /**
  * Get the ui-functions.
  *
@@ -103,6 +116,47 @@
  */
 gpointer finch_retrieve_user_info(PurpleConnection *conn, const char *name);
 
+/**
+ * Get the tree list of the buddy list.
+ * @return  The GntTree widget.
+ * @since 2.4.0
+ */
+GntTree * finch_blist_get_tree(void);
+
+/**
+ * Add an alternate buddy list manager.
+ *
+ * @param manager   The alternate buddylist manager.
+ * @since 2.4.0
+ */
+void finch_blist_install_manager(const FinchBlistManager *manager);
+
+/**
+ * Remove an alternate buddy list manager.
+ *
+ * @param manager   The buddy list manager to remove.
+ * @since 2.4.0
+ */
+void finch_blist_uninstall_manager(const FinchBlistManager *manager);
+
+/**
+ * Find a buddy list manager.
+ *
+ * @param id   The identifier for the desired buddy list manager.
+ *
+ * @return  The manager with the requested identifier, if available. @c NULL otherwise.
+ * @since 2.4.0
+ */
+FinchBlistManager * finch_blist_manager_find(const char *id);
+
+/**
+ * Request the active buddy list manager to add a node.
+ *
+ * @param node  The node to add
+ * @since 2.4.0
+ */
+void finch_blist_manager_add_node(PurpleBlistNode *node);
+
 /*@}*/
 
 #endif
--- a/finch/gntcertmgr.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/finch/gntcertmgr.c	Wed Jan 30 10:53:22 2008 +0000
@@ -25,14 +25,13 @@
  *
  */
 
-#include "internal.h"
+#include "finch.h"
 
 #include "certificate.h"
 #include "debug.h"
 #include "notify.h"
 #include "request.h"
 
-#include "finch.h"
 #include "gntcertmgr.h"
 
 #include "gntbutton.h"
@@ -235,7 +234,7 @@
 	purple_request_close_with_handle((void *)key);
 	purple_request_yes_no((void *)key, _("Confirm certificate delete"),
 			primary, NULL,
-			2,
+			1,
 			NULL, NULL, NULL,
 			g_strdup(key),
 			tls_peers_mgmt_delete_confirm_cb,
--- a/finch/gntconn.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/finch/gntconn.c	Wed Jan 30 10:53:22 2008 +0000
@@ -101,13 +101,14 @@
 }
 
 static void
-finch_connection_report_disconnect(PurpleConnection *gc, const char *text)
+finch_connection_report_disconnect(PurpleConnection *gc, PurpleConnectionError reason,
+		const char *text)
 {
 	FinchAutoRecon *info;
 	PurpleAccount *account = purple_connection_get_account(gc);
 	GList *list;
 
-	if (!gc->wants_to_die) {
+	if (!purple_connection_error_is_fatal(reason)) {
 		info = g_hash_table_lookup(hash, account);
 
 		if (info == NULL) {
@@ -148,7 +149,7 @@
 	while (list) {
 		PurpleConversation *conv = list->data;
 		list = list->next;
-		if (conv->account != account ||
+		if (purple_conversation_get_account(conv) != account ||
 				purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv)))
 			continue;
 		purple_conversation_set_data(conv, "want-to-rejoin", GINT_TO_POINTER(TRUE));
@@ -179,10 +180,10 @@
 	NULL, /* connected */
 	NULL, /* disconnected */
 	NULL, /* notice */
-	finch_connection_report_disconnect,
+	NULL,
 	NULL, /* network_connected */
 	NULL, /* network_disconnected */
-	NULL,
+	finch_connection_report_disconnect,
 	NULL,
 	NULL,
 	NULL
--- a/finch/gntconv.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/finch/gntconv.c	Wed Jan 30 10:53:22 2008 +0000
@@ -71,6 +71,20 @@
 static int color_message_action;
 static int color_timestamp;
 
+static PurpleBuddy *
+find_buddy_for_conversation(PurpleConversation *conv)
+{
+	return purple_find_buddy(purple_conversation_get_account(conv),
+			purple_conversation_get_name(conv));
+}
+
+static PurpleChat *
+find_chat_for_conversation(PurpleConversation *conv)
+{
+	return purple_blist_find_chat(purple_conversation_get_account(conv),
+			purple_conversation_get_name(conv));
+}
+
 static PurpleBlistNode *
 get_conversation_blist_node(PurpleConversation *conv)
 {
@@ -78,11 +92,11 @@
 
 	switch (purple_conversation_get_type(conv)) {
 		case PURPLE_CONV_TYPE_IM:
-			node = (PurpleBlistNode*)purple_find_buddy(conv->account, conv->name);
-			node = node ? node->parent : NULL;
+			node = (PurpleBlistNode*)find_buddy_for_conversation(conv);
+			node = node ? purple_blist_node_get_parent(node) : NULL;
 			break;
 		case PURPLE_CONV_TYPE_CHAT:
-			node = (PurpleBlistNode*)purple_blist_find_chat(conv->account, conv->name);
+			node = (PurpleBlistNode*)find_chat_for_conversation(conv);
 			break;
 		default:
 			break;
@@ -168,7 +182,7 @@
 		}
 		g_free(error);
 	}
-	else if (!purple_account_is_connected(ggconv->active_conv->account))
+	else if (!purple_account_is_connected(purple_conversation_get_account(ggconv->active_conv)))
 	{
 		purple_conversation_write(ggconv->active_conv, "", _("Message was not sent, because you are not signed on."),
 				PURPLE_MESSAGE_ERROR | PURPLE_MESSAGE_NO_LOG, time(NULL));
@@ -236,11 +250,12 @@
 	if (!buddy)
 		return NULL;
 
-	for (node = ((PurpleBlistNode*)buddy)->parent->child; node; node = node->next) {
+	for (node = purple_blist_node_get_first_child(purple_blist_node_get_parent((PurpleBlistNode*)buddy));
+				node; node = purple_blist_node_get_sibling_next(node)) {
 		if (node == (PurpleBlistNode*)buddy)
 			continue;
 		if ((ret = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
-				((PurpleBuddy*)node)->name, ((PurpleBuddy*)node)->account)) != NULL)
+				purple_buddy_get_name((PurpleBuddy*)node), purple_buddy_get_account((PurpleBuddy*)node))) != NULL)
 			break;
 	}
 	return ret;
@@ -267,7 +282,7 @@
 		return;
 
 	im = PURPLE_CONV_IM(conv);
-	ggc = conv->ui_data;
+	ggc = FINCH_GET_DATA(conv);
 
 	if (purple_conv_im_get_typing_state(im) == PURPLE_TYPING) {
 		int scroll;
@@ -303,10 +318,10 @@
 static void
 buddy_signed_on_off(PurpleBuddy *buddy, gpointer null)
 {
-	PurpleConversation *conv = find_conv_with_contact(buddy->account, buddy->name);
+	PurpleConversation *conv = find_conv_with_contact(purple_buddy_get_account(buddy), purple_buddy_get_name(buddy));
 	if (conv == NULL)
 		return;
-	generate_send_to_menu(conv->ui_data);
+	generate_send_to_menu(FINCH_GET_DATA(conv));
 }
 
 static void
@@ -315,9 +330,10 @@
 	GList *list = purple_get_ims();
 	while (list) {
 		PurpleConversation *conv = list->data;
-		PurpleConversation *cc = find_conv_with_contact(conv->account, conv->name);
+		PurpleConversation *cc = find_conv_with_contact(
+				purple_conversation_get_account(conv), purple_conversation_get_name(conv));
 		if (cc)
-			generate_send_to_menu(cc->ui_data);
+			generate_send_to_menu(FINCH_GET_DATA(cc));
 		list = list->next;
 	}
 
@@ -331,16 +347,17 @@
 			GHashTable *comps = NULL;
 
 			list = list->next;
-			if (conv->account != gc->account ||
+			if (purple_conversation_get_account(conv) != purple_connection_get_account(gc) ||
 					!purple_conversation_get_data(conv, "want-to-rejoin"))
 				continue;
 
-			chat = purple_blist_find_chat(conv->account, conv->name);
+			chat = find_chat_for_conversation(conv);
 			if (chat == NULL) {
-				if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults != NULL)
-					comps = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, conv->name);
+				PurplePluginProtocolInfo *info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
+				if (info->chat_info_defaults != NULL)
+					comps = info->chat_info_defaults(gc, purple_conversation_get_name(conv));
 			} else {
-				comps = chat->components;
+				comps = purple_chat_get_components(chat);
 			}
 			serv_join_chat(gc, comps);
 			if (chat == NULL && comps != NULL)
@@ -413,15 +430,11 @@
 
 		purple_conversation_write(conv, NULL,
 				_("Logging started. Future messages in this conversation will be logged."),
-				conv->logs ? (PURPLE_MESSAGE_SYSTEM) :
-				(PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG),
-				time(NULL));
+				PURPLE_MESSAGE_SYSTEM, time(NULL));
 	} else {
 		purple_conversation_write(conv, NULL,
 				_("Logging stopped. Future messages in this conversation will not be logged."),
-				conv->logs ? (PURPLE_MESSAGE_SYSTEM) :
-				(PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG),
-				time(NULL));
+				PURPLE_MESSAGE_SYSTEM, time(NULL));
 
 		/* Disable the logging second, so that the above message can be logged. */
 		purple_conversation_set_logging(conv, FALSE);
@@ -462,7 +475,8 @@
 	GSList *buds;
 	GList *list = NULL;
 
-	buds = purple_find_buddies(ggc->active_conv->account, ggc->active_conv->name);
+	buds = purple_find_buddies(purple_conversation_get_account(ggc->active_conv),
+			purple_conversation_get_name(ggc->active_conv));
 	if (!buds)
 		return;
 
@@ -476,7 +490,8 @@
 
 	for (; buds; buds = g_slist_delete_link(buds, buds)) {
 		PurpleBlistNode *node = (PurpleBlistNode *)purple_buddy_get_contact((PurpleBuddy *)buds->data);
-		for (node = node->child; node != NULL; node = node->next) {
+		for (node = purple_blist_node_get_first_child(node); node != NULL;
+				node = purple_blist_node_get_sibling_next(node)) {
 			PurpleBuddy *buddy = (PurpleBuddy *)node;
 			PurpleAccount *account = purple_buddy_get_account(buddy);
 			if (purple_account_is_connected(account)) {
@@ -529,7 +544,9 @@
 
 	if (purple_conversation_get_type(ggc->active_conv) == PURPLE_CONV_TYPE_IM) {
 		PurpleAccount *account = purple_conversation_get_account(ggc->active_conv);
-		PurplePluginProtocolInfo *pinfo = account->gc ? PURPLE_PLUGIN_PROTOCOL_INFO(account->gc->prpl) : NULL;
+		PurpleConnection *gc = purple_account_get_connection(account);
+		PurplePluginProtocolInfo *pinfo =
+			gc ? PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc)) : NULL;
 
 		if (pinfo && pinfo->get_info) {
 			item = gnt_menuitem_new(_("Get Info"));
@@ -543,7 +560,7 @@
 
 		if (pinfo && pinfo->send_file &&
 				(!pinfo->can_receive_file ||
-				 	pinfo->can_receive_file(account->gc, purple_conversation_get_name(ggc->active_conv)))) {
+					pinfo->can_receive_file(gc, purple_conversation_get_name(ggc->active_conv)))) {
 			item = gnt_menuitem_new(_("Send File"));
 			gnt_menu_add_item(GNT_MENU(sub), item);
 			gnt_menuitem_set_callback(item, send_file_cb, ggc);
@@ -593,7 +610,7 @@
 static void
 finch_create_conversation(PurpleConversation *conv)
 {
-	FinchConv *ggc = conv->ui_data;
+	FinchConv *ggc = FINCH_GET_DATA(conv);
 	char *title;
 	PurpleConversationType type;
 	PurpleConversation *cc;
@@ -603,9 +620,10 @@
 	if (ggc)
 		return;
 
-	cc = find_conv_with_contact(conv->account, conv->name);
-	if (cc && cc->ui_data)
-		ggc = cc->ui_data;
+	account = purple_conversation_get_account(conv);
+	cc = find_conv_with_contact(account, purple_conversation_get_name(conv));
+	if (cc && FINCH_GET_DATA(cc))
+		ggc = FINCH_GET_DATA(cc);
 	else
 		ggc = g_new0(FinchConv, 1);
 
@@ -617,14 +635,13 @@
 
 	ggc->list = g_list_prepend(ggc->list, conv);
 	ggc->active_conv = conv;
-	conv->ui_data = ggc;
+	FINCH_SET_DATA(conv, ggc);
 
-	if (cc && cc->ui_data) {
+	if (cc && FINCH_GET_DATA(cc)) {
 		finch_conversation_set_active(conv);
 		return;
 	}
 
-	account = purple_conversation_get_account(conv);
 	type = purple_conversation_get_type(conv);
 	title = get_conversation_title(conv, account);
 
@@ -633,7 +650,7 @@
 	gnt_box_set_toplevel(GNT_BOX(ggc->window), TRUE);
 	gnt_box_set_pad(GNT_BOX(ggc->window), 0);
 
-	switch(conv->type){
+	switch (purple_conversation_get_type(conv)) {
 		case PURPLE_CONV_TYPE_UNKNOWN:
 			gnt_widget_set_name(ggc->window, "conversation-window-unknown" );
 			break;
@@ -721,7 +738,7 @@
 finch_destroy_conversation(PurpleConversation *conv)
 {
 	/* do stuff here */
-	FinchConv *ggc = conv->ui_data;
+	FinchConv *ggc = FINCH_GET_DATA(conv);
 	ggc->list = g_list_remove(ggc->list, conv);
 	if (ggc->list && conv == ggc->active_conv)
 		ggc->active_conv = ggc->list->data;
@@ -738,7 +755,7 @@
 finch_write_common(PurpleConversation *conv, const char *who, const char *message,
 		PurpleMessageFlags flags, time_t mtime)
 {
-	FinchConv *ggconv = conv->ui_data;
+	FinchConv *ggconv = FINCH_GET_DATA(conv);
 	char *strip, *newline;
 	GntTextFormatFlags fl = 0;
 	int pos;
@@ -891,7 +908,7 @@
 static void
 finch_chat_add_users(PurpleConversation *conv, GList *users, gboolean new_arrivals)
 {
-	FinchConv *ggc = conv->ui_data;
+	FinchConv *ggc = FINCH_GET_DATA(conv);
 	GntEntry *entry = GNT_ENTRY(ggc->entry);
 
 	if (!new_arrivals)
@@ -930,7 +947,7 @@
 finch_chat_rename_user(PurpleConversation *conv, const char *old, const char *new_n, const char *new_a)
 {
 	/* Update the name for string completion */
-	FinchConv *ggc = conv->ui_data;
+	FinchConv *ggc = FINCH_GET_DATA(conv);
 	GntEntry *entry = GNT_ENTRY(ggc->entry);
 	GntTree *tree = GNT_TREE(ggc->u.chat->userlist);
 	PurpleConvChatBuddy *cb = purple_conv_chat_cb_find(PURPLE_CONV_CHAT(conv), new_n);
@@ -948,7 +965,7 @@
 finch_chat_remove_users(PurpleConversation *conv, GList *list)
 {
 	/* Remove the name from string completion */
-	FinchConv *ggc = conv->ui_data;
+	FinchConv *ggc = FINCH_GET_DATA(conv);
 	GntEntry *entry = GNT_ENTRY(ggc->entry);
 	for (; list; list = list->next) {
 		GntTree *tree = GNT_TREE(ggc->u.chat->userlist);
@@ -961,7 +978,7 @@
 finch_chat_update_user(PurpleConversation *conv, const char *user)
 {
 	PurpleConvChatBuddy *cb = purple_conv_chat_cb_find(PURPLE_CONV_CHAT(conv), user);
-	FinchConv *ggc = conv->ui_data;
+	FinchConv *ggc = FINCH_GET_DATA(conv);
 	gnt_tree_change_text(GNT_TREE(ggc->u.chat->userlist), (gpointer)user, 0, chat_flag_text(cb->flags));
 }
 
@@ -1070,7 +1087,7 @@
 clear_command_cb(PurpleConversation *conv,
                  const char *cmd, char **args, char **error, void *data)
 {
-	FinchConv *ggconv = conv->ui_data;
+	FinchConv *ggconv = FINCH_GET_DATA(conv);
 	gnt_text_view_clear(GNT_TEXT_VIEW(ggconv->tv));
 	purple_conversation_clear_message_history(conv);
 	return PURPLE_CMD_STATUS_OK;
@@ -1127,7 +1144,7 @@
 static PurpleCmdRet
 users_command_cb(PurpleConversation *conv, const char *cmd, char **args, char **error, gpointer data)
 {
-	FinchConv *fc = conv->ui_data;
+	FinchConv *fc = FINCH_GET_DATA(conv);
 	FinchConvChat *ch;
 	if (!fc)
 		return PURPLE_CMD_STATUS_FAILED;
@@ -1231,7 +1248,7 @@
 
 void finch_conversation_set_active(PurpleConversation *conv)
 {
-	FinchConv *ggconv = conv->ui_data;
+	FinchConv *ggconv = FINCH_GET_DATA(conv);
 	PurpleAccount *account;
 	char *title;
 
@@ -1247,7 +1264,7 @@
 
 void finch_conversation_set_info_widget(PurpleConversation *conv, GntWidget *widget)
 {
-	FinchConv *fc = conv->ui_data;
+	FinchConv *fc = FINCH_GET_DATA(conv);
 	int height, width;
 
 	gnt_box_remove_all(GNT_BOX(fc->info));
--- a/finch/gntft.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/finch/gntft.c	Wed Jan 30 10:53:22 2008 +0000
@@ -23,13 +23,14 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+#include "finch.h"
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
 #include <gntcheckbox.h>
 #include <gntlabel.h>
 #include <gnttree.h>
-#include "internal.h"
 
 #include "debug.h"
 #include "notify.h"
@@ -41,7 +42,7 @@
 #include "prefs.h"
 
 #define FINCHXFER(xfer) \
-	(PurpleGntXferUiData *)(xfer)->ui_data
+	(PurpleGntXferUiData *)FINCH_GET_DATA(xfer)
 
 typedef struct
 {
@@ -168,9 +169,15 @@
 stop_button_cb(GntButton *button)
 {
 	PurpleXfer *selected_xfer = gnt_tree_get_selection_data(GNT_TREE(xfer_dialog->tree));
-	if (selected_xfer && selected_xfer->status != PURPLE_XFER_STATUS_CANCEL_LOCAL &&
-			selected_xfer->status != PURPLE_XFER_STATUS_CANCEL_REMOTE &&
-			selected_xfer->status != PURPLE_XFER_STATUS_DONE)
+	PurpleXferStatusType status;
+
+	if (!selected_xfer)
+		return;
+
+	status = purple_xfer_get_status(selected_xfer);
+	if (status != PURPLE_XFER_STATUS_CANCEL_LOCAL &&
+			status != PURPLE_XFER_STATUS_CANCEL_REMOTE &&
+			status != PURPLE_XFER_STATUS_DONE)
 		purple_xfer_cancel_local(selected_xfer);
 }
 
@@ -397,14 +404,12 @@
 	time_t elapsed, now;
 	char *kbsec;
 
-	if (xfer->end_time != 0)
-		now = xfer->end_time;
-	else
+	if ((now = purple_xfer_get_end_time(xfer)) == 0)
 		now = time(NULL);
 
 	kb_sent = purple_xfer_get_bytes_sent(xfer) / 1024.0;
 	kb_rem  = purple_xfer_get_bytes_remaining(xfer) / 1024.0;
-	elapsed = (xfer->start_time > 0 ? now - xfer->start_time : 0);
+	elapsed = (purple_xfer_get_start_time(xfer) > 0 ? now - purple_xfer_get_start_time(xfer) : 0);
 	kbps    = (elapsed > 0 ? (kb_sent / elapsed) : 0);
 
 	g_return_if_fail(xfer_dialog != NULL);
@@ -463,7 +468,7 @@
 
 	/* This is where we're setting xfer->ui_data for the first time. */
 	data = g_new0(PurpleGntXferUiData, 1);
-	xfer->ui_data = data;
+	FINCH_SET_DATA(xfer, data);
 }
 
 static void
@@ -475,7 +480,7 @@
 	if (data) {
 		g_free(data->name);
 		g_free(data);
-		xfer->ui_data = NULL;
+		FINCH_SET_DATA(xfer, NULL);
 	}
 }
 
--- a/finch/gntidle.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/finch/gntidle.c	Wed Jan 30 10:53:22 2008 +0000
@@ -21,7 +21,7 @@
  *
  */
 
-#include "internal.h"
+#include "finch.h"
 #include "gntidle.h"
 #include "gntwm.h"
 
--- a/finch/gntpounce.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/finch/gntpounce.c	Wed Jan 30 10:53:22 2008 +0000
@@ -35,7 +35,6 @@
 #include <gnttree.h>
 #include <gntutils.h>
 
-#include "internal.h"
 #include "finch.h"
 
 #include "account.h"
@@ -808,39 +807,42 @@
 
 	if (purple_pounce_action_is_enabled(pounce, "popup-notify"))
 	{
-		char *tmp;
+		char *tmp = NULL;
 		const char *name_shown;
 		const char *reason;
+		struct {
+			PurplePounceEvent event;
+			const char *format;
+		} messages[] = {
+			{PURPLE_POUNCE_TYPING, _("%s has started typing to you (%s)")},
+			{PURPLE_POUNCE_TYPED, _("%s has paused while typing to you (%s)")},
+			{PURPLE_POUNCE_SIGNON, _("%s has signed on (%s)")},
+			{PURPLE_POUNCE_IDLE_RETURN, _("%s has returned from being idle (%s)")},
+			{PURPLE_POUNCE_AWAY_RETURN, _("%s has returned from being away (%s)")},
+			{PURPLE_POUNCE_TYPING_STOPPED, _("%s has stopped typing to you (%s)")},
+			{PURPLE_POUNCE_SIGNOFF, _("%s has signed off (%s)")},
+			{PURPLE_POUNCE_IDLE, _("%s has become idle (%s)")},
+			{PURPLE_POUNCE_AWAY, _("%s has gone away. (%s)")},
+			{PURPLE_POUNCE_MESSAGE_RECEIVED, _("%s has sent you a message. (%s)")},
+			{0, NULL}
+		};
+		int i;
 		reason = purple_pounce_action_get_attribute(pounce, "popup-notify",
-														  "reason");
+				"reason");
 
 		/*
 		 * Here we place the protocol name in the pounce dialog to lessen
 		 * confusion about what protocol a pounce is for.
 		 */
-		tmp = g_strdup_printf(
-				   (events & PURPLE_POUNCE_TYPING) ?
-				   _("%s has started typing to you (%s)") :
-				   (events & PURPLE_POUNCE_TYPED) ?
-				   _("%s has paused while typing to you (%s)") :
-				   (events & PURPLE_POUNCE_SIGNON) ?
-				   _("%s has signed on (%s)") :
-				   (events & PURPLE_POUNCE_IDLE_RETURN) ?
-				   _("%s has returned from being idle (%s)") :
-				   (events & PURPLE_POUNCE_AWAY_RETURN) ?
-				   _("%s has returned from being away (%s)") :
-				   (events & PURPLE_POUNCE_TYPING_STOPPED) ?
-				   _("%s has stopped typing to you (%s)") :
-				   (events & PURPLE_POUNCE_SIGNOFF) ?
-				   _("%s has signed off (%s)") :
-				   (events & PURPLE_POUNCE_IDLE) ?
-				   _("%s has become idle (%s)") :
-				   (events & PURPLE_POUNCE_AWAY) ?
-				   _("%s has gone away. (%s)") :
-				   (events & PURPLE_POUNCE_MESSAGE_RECEIVED) ?
-				   _("%s has sent you a message. (%s)") :
-				   _("Unknown pounce event. Please report this!"),
-				   alias, purple_account_get_protocol_name(account));
+		for (i = 0; messages[i].format != NULL; i++) {
+			if (messages[i].event & events) {
+				tmp = g_strdup_printf(messages[i].format, alias,
+						purple_account_get_protocol_name(account));
+				break;
+			}
+		}
+		if (tmp == NULL)
+			tmp = g_strdup(_("Unknown pounce event. Please report this!"));
 
 		/*
 		 * Ok here is where I change the second argument, title, from
@@ -880,7 +882,7 @@
 			purple_conversation_write(conv, NULL, message,
 									PURPLE_MESSAGE_SEND, time(NULL));
 
-			serv_send_im(account->gc, (char *)pouncee, (char *)message, 0);
+			serv_send_im(purple_account_get_connection(account), (char *)pouncee, (char *)message, 0);
 		}
 	}
 
--- a/finch/gntrequest.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/finch/gntrequest.c	Wed Jan 30 10:53:22 2008 +0000
@@ -253,7 +253,7 @@
 		void *user_data, size_t actioncount,
 		va_list actions)
 {
-	GntWidget *window, *box, *button;
+	GntWidget *window, *box, *button, *focus = NULL;
 	int i;
 
 	window = setup_request_window(title, primary, secondary, PURPLE_REQUEST_ACTION);
@@ -272,9 +272,14 @@
 		g_object_set_data(G_OBJECT(button), "activate-userdata", user_data);
 		g_object_set_data(G_OBJECT(button), "activate-id", GINT_TO_POINTER(i));
 		g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(request_action_cb), window);
+
+		if (i == default_value)
+			focus = button;
 	}
 
 	gnt_widget_show(window);
+	if (focus)
+		gnt_box_give_focus_to_child(GNT_BOX(window), focus);
 
 	return window;
 }
@@ -303,26 +308,26 @@
 			PurpleRequestFieldType type = purple_request_field_get_type(field);
 			if (type == PURPLE_REQUEST_FIELD_BOOLEAN)
 			{
-				GntWidget *check = field->ui_data;
+				GntWidget *check = FINCH_GET_DATA(field);
 				gboolean value = gnt_check_box_get_checked(GNT_CHECK_BOX(check));
 				purple_request_field_bool_set_value(field, value);
 			}
 			else if (type == PURPLE_REQUEST_FIELD_STRING)
 			{
-				GntWidget *entry = field->ui_data;
+				GntWidget *entry = FINCH_GET_DATA(field);
 				const char *text = gnt_entry_get_text(GNT_ENTRY(entry));
 				purple_request_field_string_set_value(field, (text && *text) ? text : NULL);
 			}
 			else if (type == PURPLE_REQUEST_FIELD_INTEGER)
 			{
-				GntWidget *entry = field->ui_data;
+				GntWidget *entry = FINCH_GET_DATA(field);
 				const char *text = gnt_entry_get_text(GNT_ENTRY(entry));
 				int value = (text && *text) ? atoi(text) : 0;
 				purple_request_field_int_set_value(field, value);
 			}
 			else if (type == PURPLE_REQUEST_FIELD_CHOICE)
 			{
-				GntWidget *combo = field->ui_data;
+				GntWidget *combo = FINCH_GET_DATA(field);
 				int id;
 				id = GPOINTER_TO_INT(gnt_combo_box_get_selected_data(GNT_COMBO_BOX(combo)));
 				purple_request_field_choice_set_value(field, id);
@@ -333,7 +338,7 @@
 				if (purple_request_field_list_get_multi_select(field))
 				{
 					GList *iter;
-					GntWidget *tree = field->ui_data;
+					GntWidget *tree = FINCH_GET_DATA(field);
 
 					iter = purple_request_field_list_get_items(field);
 					for (; iter; iter = iter->next)
@@ -346,7 +351,7 @@
 				}
 				else
 				{
-					GntWidget *combo = field->ui_data;
+					GntWidget *combo = FINCH_GET_DATA(field);
 					gpointer data = gnt_combo_box_get_selected_data(GNT_COMBO_BOX(combo));
 					list = g_list_append(list, data);
 				}
@@ -356,7 +361,7 @@
 			}
 			else if (type == PURPLE_REQUEST_FIELD_ACCOUNT)
 			{
-				GntWidget *combo = field->ui_data;
+				GntWidget *combo = FINCH_GET_DATA(field);
 				PurpleAccount *acc = gnt_combo_box_get_selected_data(GNT_COMBO_BOX(combo));
 				purple_request_field_account_set_value(field, acc);
 			}
@@ -419,9 +424,10 @@
 			*screenname = entry;
 	} else if (hint && !strcmp(hint, "group")) {
 		PurpleBlistNode *node;
-		for (node = purple_blist_get_root(); node; node = node->next) {
+		for (node = purple_blist_get_root(); node;
+				node = purple_blist_node_get_sibling_next(node)) {
 			if (PURPLE_BLIST_NODE_IS_GROUP(node))
-				gnt_entry_add_suggest(GNT_ENTRY(entry), ((PurpleGroup *)node)->name);
+				gnt_entry_add_suggest(GNT_ENTRY(entry), purple_group_get_name((PurpleGroup *)node));
 		}
 	}
 	return entry;
@@ -588,35 +594,35 @@
 
 			if (type == PURPLE_REQUEST_FIELD_BOOLEAN)
 			{
-				field->ui_data = create_boolean_field(field);
+				FINCH_SET_DATA(field, create_boolean_field(field));
 			}
 			else if (type == PURPLE_REQUEST_FIELD_STRING)
 			{
-				field->ui_data = create_string_field(field, &screenname);
+				FINCH_SET_DATA(field, create_string_field(field, &screenname));
 			}
 			else if (type == PURPLE_REQUEST_FIELD_INTEGER)
 			{
-				field->ui_data = create_integer_field(field);
+				FINCH_SET_DATA(field, create_integer_field(field));
 			}
 			else if (type == PURPLE_REQUEST_FIELD_CHOICE)
 			{
-				field->ui_data = create_choice_field(field);
+				FINCH_SET_DATA(field, create_choice_field(field));
 			}
 			else if (type == PURPLE_REQUEST_FIELD_LIST)
 			{
-				field->ui_data = create_list_field(field);
+				FINCH_SET_DATA(field, create_list_field(field));
 			}
 			else if (type == PURPLE_REQUEST_FIELD_ACCOUNT)
 			{
-				accountlist = field->ui_data = create_account_field(field);
+				accountlist = FINCH_SET_DATA(field, create_account_field(field));
 			}
 			else
 			{
-				field->ui_data = gnt_label_new_with_format(_("Not implemented yet."),
-						GNT_TEXT_FLAG_BOLD);
+				FINCH_SET_DATA(field, gnt_label_new_with_format(_("Not implemented yet."),
+						GNT_TEXT_FLAG_BOLD));
 			}
 			gnt_box_set_alignment(GNT_BOX(hbox), GNT_ALIGN_MID);
-			gnt_box_add_widget(GNT_BOX(hbox), GNT_WIDGET(field->ui_data));
+			gnt_box_add_widget(GNT_BOX(hbox), GNT_WIDGET(FINCH_GET_DATA(field)));
 		}
 		if (grlist->next)
 			gnt_box_add_widget(GNT_BOX(box), gnt_hline_new());
--- a/finch/gntroomlist.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/finch/gntroomlist.c	Wed Jan 30 10:53:22 2008 +0000
@@ -24,7 +24,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
-#include"internal.h"
+#include "finch.h"
 
 #include "gntrequest.h"
 #include "gntroomlist.h"
@@ -116,7 +116,7 @@
 	if (gc == NULL || room == NULL)
 		return;
 
-	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
+	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
 
 	if(prpl_info != NULL && prpl_info->roomlist_room_serialize)
 		name = prpl_info->roomlist_room_serialize(room);
@@ -238,7 +238,7 @@
 		PurplePluginProtocolInfo *prpl_info = NULL;
 		PurpleConnection *gc = list->data;
 
-		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
+		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
 		if (prpl_info->roomlist_get_list != NULL) {
 			PurpleAccount *account = purple_connection_get_account(gc);
 			char *text = g_strdup_printf("%s (%s)",
@@ -287,10 +287,10 @@
 
 	froomlist.accounts = accounts = gnt_combo_box_new();
 	reset_account_list(account);
-	gnt_box_add_widget(GNT_BOX(window), froomlist.accounts);
-	g_signal_connect(G_OBJECT(froomlist.accounts), "selection-changed",
+	gnt_box_add_widget(GNT_BOX(window), accounts);
+	g_signal_connect(G_OBJECT(accounts), "selection-changed",
 			G_CALLBACK(roomlist_account_changed), NULL);
-	froomlist.account = gnt_combo_box_get_selected_data(GNT_COMBO_BOX(froomlist.accounts));
+	froomlist.account = gnt_combo_box_get_selected_data(GNT_COMBO_BOX(accounts));
 
 	froomlist.tree = tree = gnt_tree_new_with_columns(2);
 	gnt_tree_set_show_title(GNT_TREE(tree), TRUE);
@@ -341,7 +341,7 @@
 static void
 fl_create(PurpleRoomlist *list)
 {
-	list->ui_data = &froomlist;
+	FINCH_SET_DATA(list, &froomlist);
 	setup_roomlist(NULL);
 	update_roomlist(list);
 }
--- a/finch/gntsound.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/finch/gntsound.c	Wed Jan 30 10:53:22 2008 +0000
@@ -23,7 +23,6 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
-#include "internal.h"
 #include "finch.h"
 
 #ifdef _WIN32
@@ -142,13 +141,15 @@
 	char *nick = NULL;
 	char *name = NULL;
 	gboolean ret = FALSE;
+	PurpleAccount *account;
+
 	chat = purple_conversation_get_chat_data(conv);
-
 	if (chat == NULL)
 		return ret;
 
-	nick = g_strdup(purple_normalize(conv->account, chat->nick));
-	name = g_strdup(purple_normalize(conv->account, aname));
+	account = purple_conversation_get_account(conv);
+	nick = g_strdup(purple_normalize(account, chat->nick));
+	name = g_strdup(purple_normalize(account, aname));
 
 	if (g_utf8_collate(nick, name) == 0)
 		ret = TRUE;
--- a/finch/gntui.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/finch/gntui.c	Wed Jan 30 10:53:22 2008 +0000
@@ -19,7 +19,7 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
-#include "internal.h"
+#include "finch.h"
 
 #include "gntui.h"
 
--- a/finch/libgnt/gnttree.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/finch/libgnt/gnttree.c	Wed Jan 30 10:53:22 2008 +0000
@@ -39,6 +39,7 @@
 {
 	PROP_0,
 	PROP_COLUMNS,
+	PROP_EXPANDER,
 };
 
 enum
@@ -59,6 +60,7 @@
 
 	GCompareFunc compare;
 	int lastvisible;
+	int expander_level;
 };
 
 #define	TAB_SIZE 3
@@ -338,7 +340,7 @@
 						row->isselected ? 'X' : ' ');
 				fl = 4;
 			}
-			else if (row->parent == NULL && row->child)
+			else if (find_depth(row) < tree->priv->expander_level && row->child)
 			{
 				if (row->collapsed)
 				{
@@ -951,6 +953,11 @@
 		case PROP_COLUMNS:
 			_gnt_tree_init_internals(tree, g_value_get_int(value));
 			break;
+		case PROP_EXPANDER:
+			if (tree->priv->expander_level == g_value_get_int(value))
+				break;
+			tree->priv->expander_level = g_value_get_int(value);
+			g_object_notify(obj, "expander-level");
 		default:
 			break;
 	}
@@ -965,6 +972,9 @@
 		case PROP_COLUMNS:
 			g_value_set_int(value, tree->ncol);
 			break;
+		case PROP_EXPANDER:
+			g_value_set_int(value, tree->priv->expander_level);
+			break;
 		default:
 			break;
 	}
@@ -995,6 +1005,14 @@
 				G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
 			)
 		);
+	g_object_class_install_property(gclass,
+			PROP_EXPANDER,
+			g_param_spec_int("expander-level", "Expander level",
+				"Number of levels to show expander in the tree.",
+				0, G_MAXINT, 1,
+				G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
+			)
+		);
 
 	signals[SIG_SELECTION_CHANGED] = 
 		g_signal_new("selection-changed",
@@ -1618,6 +1636,7 @@
 {
 	GntWidget *widget = g_object_new(GNT_TYPE_TREE,
 			"columns", col,
+			"expander-level", 1,
 			NULL);
 
 	return widget;
@@ -1841,3 +1860,9 @@
 	tree->priv->search_func = func;
 }
 
+gpointer gnt_tree_get_parent_key(GntTree *tree, gpointer key)
+{
+	GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
+	return (row && row->parent) ? row->parent->key : NULL;
+}
+
--- a/finch/libgnt/gnttree.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/finch/libgnt/gnttree.h	Wed Jan 30 10:53:22 2008 +0000
@@ -575,6 +575,17 @@
 void gnt_tree_set_search_function(GntTree *tree,
 		gboolean (*func)(GntTree *tree, gpointer key, const char *search, const char *current));
 
+/**
+ * Get the parent key for a row.
+ *
+ * @param  tree  The tree
+ * @param  key   The key for the row.
+ *
+ * @return The key of the parent row.
+ * @since 2.4.0
+ */
+gpointer gnt_tree_get_parent_key(GntTree *tree, gpointer key);
+
 G_END_DECLS
 
 #endif /* GNT_TREE_H */
--- a/finch/plugins/Makefile.am	Thu Jan 10 05:32:41 2008 +0000
+++ b/finch/plugins/Makefile.am	Wed Jan 30 10:53:22 2008 +0000
@@ -1,7 +1,8 @@
 gntclipboard_la_LDFLAGS = -module -avoid-version
 gntgf_la_LDFLAGS      = -module -avoid-version
 gnthistory_la_LDFLAGS = -module -avoid-version
-gntlastlog_la_LDFLAGS    = -module -avoid-version
+gntlastlog_la_LDFLAGS = -module -avoid-version
+grouping_la_LDFLAGS   = -module -avoid-version
 
 if PLUGINS
 
@@ -9,7 +10,8 @@
 	gntclipboard.la \
 	gntgf.la \
 	gnthistory.la \
-	gntlastlog.la
+	gntlastlog.la \
+	grouping.la
 
 plugindir = $(libdir)/finch
 
@@ -17,6 +19,7 @@
 gntgf_la_SOURCES      = gntgf.c
 gnthistory_la_SOURCES = gnthistory.c
 gntlastlog_la_SOURCES = lastlog.c
+grouping_la_SOURCES   = grouping.c
 
 gntclipboard_la_CFLAGS = $(X11_CFLAGS)
 gntgf_la_CFLAGS = $(X11_CFLAGS)
@@ -25,6 +28,7 @@
 gntgf_la_LIBADD       = $(GLIB_LIBS) $(X11_LIBS) $(top_builddir)/finch/libgnt/libgnt.la
 gnthistory_la_LIBADD  = $(GLIB_LIBS)
 gntlastlog_la_LIBADD  = $(GLIB_LIBS)
+grouping_la_LIBADD    = $(GLIB_LIBS) $(top_builddir)/finch/libgnt/libgnt.la
 
 endif # PLUGINS
 
--- a/finch/plugins/gnthistory.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/finch/plugins/gnthistory.c	Wed Jan 30 10:53:22 2008 +0000
@@ -51,8 +51,7 @@
 	PurpleMessageFlags mflag;
 
 	convtype = purple_conversation_get_type(c);
-	if (convtype == PURPLE_CONV_TYPE_IM)
-	{
+	if (convtype == PURPLE_CONV_TYPE_IM) {
 		GSList *buddies;
 		GSList *cur;
 
@@ -62,17 +61,17 @@
 			return;
 
 		/* Find buddies for this conversation. */
-	        buddies = purple_find_buddies(account, name);
+		buddies = purple_find_buddies(account, name);
 
 		/* If we found at least one buddy, save the first buddy's alias. */
 		if (buddies != NULL)
 			alias = purple_buddy_get_contact_alias((PurpleBuddy *)buddies->data);
 
-	        for (cur = buddies; cur != NULL; cur = cur->next)
-	        {
-	                PurpleBlistNode *node = cur->data;
-	                if ((node != NULL) && ((node->prev != NULL) || (node->next != NULL)))
-	                {
+		for (cur = buddies; cur != NULL; cur = cur->next) {
+			PurpleBlistNode *node = cur->data;
+			if ((node != NULL) &&
+					((purple_blist_node_get_sibling_prev(node) != NULL) ||
+						(purple_blist_node_get_sibling_next(node) != NULL))) {
 				PurpleBlistNode *node2;
 
 				alias = purple_buddy_get_contact_alias((PurpleBuddy *)node);
@@ -80,26 +79,24 @@
 				/* We've found a buddy that matches this conversation.  It's part of a
 				 * PurpleContact with more than one PurpleBuddy.  Loop through the PurpleBuddies
 				 * in the contact and get all the logs. */
-				for (node2 = node->parent->child ; node2 != NULL ; node2 = node2->next)
-				{
+				for (node2 = purple_blist_node_get_first_child(purple_blist_node_get_parent(node));
+						node2 != NULL ; node2 = purple_blist_node_get_sibling_next(node2)) {
 					logs = g_list_concat(
-						purple_log_get_logs(PURPLE_LOG_IM,
-							purple_buddy_get_name((PurpleBuddy *)node2),
-							purple_buddy_get_account((PurpleBuddy *)node2)),
-						logs);
+							purple_log_get_logs(PURPLE_LOG_IM,
+								purple_buddy_get_name((PurpleBuddy *)node2),
+								purple_buddy_get_account((PurpleBuddy *)node2)),
+							logs);
 				}
 				break;
-	                }
-	        }
-	        g_slist_free(buddies);
+			}
+		}
+		g_slist_free(buddies);
 
 		if (logs == NULL)
 			logs = purple_log_get_logs(PURPLE_LOG_IM, name, account);
 		else
 			logs = g_list_sort(logs, purple_log_compare);
-	}
-	else if (convtype == PURPLE_CONV_TYPE_CHAT)
-	{
+	} else if (convtype == PURPLE_CONV_TYPE_CHAT) {
 		/* If we're not logging, don't show anything.
 		 * Otherwise, we might show a very old log. */
 		if (!purple_prefs_get_bool("/purple/logging/log_chats"))
@@ -115,7 +112,7 @@
 	history = purple_log_read((PurpleLog*)logs->data, &flags);
 
 	header = g_strdup_printf(_("<b>Conversation with %s on %s:</b><br>"), alias,
-							 purple_date_format_full(localtime(&((PurpleLog *)logs->data)->time)));
+			purple_date_format_full(localtime(&((PurpleLog *)logs->data)->time)));
 	purple_conversation_write(c, "", header, mflag, time(NULL));
 	g_free(header);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/plugins/grouping.c	Wed Jan 30 10:53:22 2008 +0000
@@ -0,0 +1,278 @@
+/**
+ * @file grouping.c  Provides different grouping options.
+ *
+ * Copyright (C) 2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#define PURPLE_PLUGIN
+
+#include "internal.h"
+#include "purple.h"
+
+#include "gntblist.h"
+#include "gntplugin.h"
+
+#include "gnttree.h"
+
+/**
+ * Online/Offline
+ */
+static PurpleBlistNode online = {.type = PURPLE_BLIST_OTHER_NODE},
+					   offline = {.type = PURPLE_BLIST_OTHER_NODE};
+
+static gboolean on_offline_init()
+{
+	GntTree *tree = finch_blist_get_tree();
+
+	gnt_tree_add_row_after(tree, &online,
+			gnt_tree_create_row(tree, _("Online")), NULL, NULL);
+	gnt_tree_add_row_after(tree, &offline,
+			gnt_tree_create_row(tree, _("Offline")), NULL, &online);
+
+	return TRUE;
+}
+
+static gboolean on_offline_can_add_node(PurpleBlistNode *node)
+{
+	switch (purple_blist_node_get_type(node)) {
+		case PURPLE_BLIST_CONTACT_NODE:
+			{
+				PurpleContact *contact = (PurpleContact*)node;
+				if (contact->currentsize > 0)
+					return TRUE;
+				return FALSE;
+			}
+			break;
+		case PURPLE_BLIST_BUDDY_NODE:
+			{
+				PurpleBuddy *buddy = (PurpleBuddy*)node;
+				if (PURPLE_BUDDY_IS_ONLINE(buddy))
+					return TRUE;
+				if (purple_prefs_get_bool("/finch/blist/showoffline") &&
+						purple_account_is_connected(purple_buddy_get_account(buddy)))
+					return TRUE;
+				return FALSE;
+			}
+			break;
+		case PURPLE_BLIST_CHAT_NODE:
+			{
+				PurpleChat *chat = (PurpleChat*)node;
+				return purple_account_is_connected(purple_chat_get_account(chat));
+			}
+			break;
+		default:
+			return FALSE;
+	}
+}
+
+static gpointer on_offline_find_parent(PurpleBlistNode *node)
+{
+	gpointer ret = NULL;
+
+	switch (purple_blist_node_get_type(node)) {
+		case PURPLE_BLIST_CONTACT_NODE:
+			node = (PurpleBlistNode*)purple_contact_get_priority_buddy((PurpleContact*)node);
+			ret = PURPLE_BUDDY_IS_ONLINE((PurpleBuddy*)node) ? &online : &offline;
+			break;
+		case PURPLE_BLIST_BUDDY_NODE:
+			ret = purple_blist_node_get_parent(node);
+			finch_blist_manager_add_node(ret);
+			break;
+		case PURPLE_BLIST_CHAT_NODE:
+			ret = &online;
+			break;
+		default:
+			break;
+	}
+	return ret;
+}
+
+static gboolean on_offline_create_tooltip(gpointer selected_row, GString **body, char **tool_title)
+{
+	static FinchBlistManager *def = NULL;
+	PurpleBlistNode *node = selected_row;
+
+	if (def == NULL)
+		def = finch_blist_manager_find("default");
+
+	if (purple_blist_node_get_type(node) == PURPLE_BLIST_OTHER_NODE) {
+		/* There should be some easy way of getting the total online count,
+		 * or total number of chats. Doing a loop here will probably be pretty
+		 * expensive. */
+		if (body)
+			*body = g_string_new(node == &online ? _("Online Buddies") : _("Offline Buddies"));
+		return TRUE;
+	} else {
+		return def ? def->create_tooltip(selected_row, body, tool_title) : FALSE;
+	}
+}
+
+static FinchBlistManager on_offline =
+{
+	"on-offline",
+	N_("Online/Offline"),
+	on_offline_init,
+	NULL,
+	on_offline_can_add_node,
+	on_offline_find_parent,
+	on_offline_create_tooltip,
+	{NULL, NULL, NULL, NULL}
+};
+
+/**
+ * Meebo-like Grouping.
+ */
+static PurpleBlistNode meebo = {.type = PURPLE_BLIST_OTHER_NODE};
+static gboolean meebo_init()
+{
+	GntTree *tree = finch_blist_get_tree();
+	if (!g_list_find(gnt_tree_get_rows(tree), &meebo)) {
+		gnt_tree_add_row_last(tree, &meebo,
+				gnt_tree_create_row(tree, _("Offline")), NULL);
+	}
+	return TRUE;
+}
+
+static gpointer meebo_find_parent(PurpleBlistNode *node)
+{
+	static FinchBlistManager *def = NULL;
+	if (def == NULL)
+		def = finch_blist_manager_find("default");
+
+	if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
+		PurpleBuddy *buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
+		if (buddy && !PURPLE_BUDDY_IS_ONLINE(buddy)) {
+			return &meebo;
+		}
+	}
+	return def->find_parent(node);
+}
+
+static FinchBlistManager meebo_group =
+{
+	"meebo",
+	N_("Meebo"),
+	meebo_init,
+	NULL,
+	NULL,
+	meebo_find_parent,
+	NULL,
+	{NULL, NULL, NULL, NULL}
+};
+
+/**
+ * No Grouping.
+ */
+static gboolean no_group_init()
+{
+	GntTree *tree = finch_blist_get_tree();
+	g_object_set(G_OBJECT(tree), "expander-level", 0, NULL);
+	return TRUE;
+}
+
+static gboolean no_group_uninit()
+{
+	GntTree *tree = finch_blist_get_tree();
+	g_object_set(G_OBJECT(tree), "expander-level", 1, NULL);
+	return TRUE;
+}
+
+static gboolean no_group_can_add_node(PurpleBlistNode *node)
+{
+	return on_offline_can_add_node(node);   /* These happen to be the same */
+}
+
+static gpointer no_group_find_parent(PurpleBlistNode *node)
+{
+	gpointer ret = NULL;
+
+	switch (purple_blist_node_get_type(node)) {
+		case PURPLE_BLIST_BUDDY_NODE:
+			ret = purple_blist_node_get_parent(node);
+			finch_blist_manager_add_node(ret);
+			break;
+		default:
+			break;
+	}
+	return ret;
+}
+
+static FinchBlistManager no_group =
+{
+	"no-group",
+	N_("No Grouping"),
+	no_group_init,
+	no_group_uninit,
+	no_group_can_add_node,
+	no_group_find_parent,
+	NULL,
+	{NULL, NULL, NULL, NULL}
+};
+
+static gboolean
+plugin_load(PurplePlugin *plugin)
+{
+	finch_blist_install_manager(&on_offline);
+	finch_blist_install_manager(&meebo_group);
+	finch_blist_install_manager(&no_group);
+	return TRUE;
+}
+
+static gboolean
+plugin_unload(PurplePlugin *plugin)
+{
+	finch_blist_uninstall_manager(&on_offline);
+	finch_blist_uninstall_manager(&meebo_group);
+	finch_blist_uninstall_manager(&no_group);
+	return TRUE;
+}
+
+static PurplePluginInfo info =
+{
+	PURPLE_PLUGIN_MAGIC,
+	PURPLE_MAJOR_VERSION,
+	PURPLE_MINOR_VERSION,
+	PURPLE_PLUGIN_STANDARD,
+	FINCH_PLUGIN_TYPE,
+	0,
+	NULL,
+	PURPLE_PRIORITY_DEFAULT,
+	"grouping",
+	N_("Grouping"),
+	VERSION,
+	N_("Provides alternate buddylist grouping options."),
+	N_("Provides alternate buddylist grouping options."),
+	"Sadrul H Chowdhury <sadrul@users.sourceforge.net>",
+	PURPLE_WEBSITE,
+	plugin_load,
+	plugin_unload,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,NULL,NULL,NULL
+};
+
+static void
+init_plugin(PurplePlugin *plugin)
+{
+}
+
+PURPLE_INIT_PLUGIN(ignore, init_plugin, info)
+
+
--- a/libpurple/account.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/account.c	Wed Jan 30 10:53:22 2008 +0000
@@ -752,7 +752,7 @@
 
 	current_error = g_new0(PurpleConnectionErrorInfo, 1);
 	current_error->type = type;
-	current_error->description = g_strdup(description);
+	current_error->description = description;
 
 	set_current_error(account, current_error);
 }
--- a/libpurple/account.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/account.h	Wed Jan 30 10:53:22 2008 +0000
@@ -410,37 +410,34 @@
 void purple_account_set_status_types(PurpleAccount *account, GList *status_types);
 
 /**
- * Activates or deactivates a status.  All changes to the statuses of
- * an account go through this function or purple_account_set_status_list.
- *
- * Only independent statuses can be deactivated with this. To deactivate
- * an exclusive status, activate a different (and exclusive?) status.
+ * Variadic version of purple_account_set_status_list(); the variadic list
+ * replaces @a attrs, and should be <tt>NULL</tt>-terminated.
  *
- * @param account   The account.
- * @param status_id The ID of the status.
- * @param active    The active state.
- * @param ...       Pairs of attributes for the new status passed in
- *                  as a NULL-terminated list of id/value pairs.
+ * @copydoc purple_account_set_status_list()
  */
 void purple_account_set_status(PurpleAccount *account, const char *status_id,
-							 gboolean active, ...) G_GNUC_NULL_TERMINATED;
+	gboolean active, ...) G_GNUC_NULL_TERMINATED;
 
 
 /**
  * Activates or deactivates a status.  All changes to the statuses of
- * an account go through this function or purple_account_set_status.
+ * an account go through this function or purple_account_set_status().
  *
- * Only independent statuses can be deactivated with this. To deactivate
- * an exclusive status, activate a different (and exclusive?) status.
+ * You can only deactivate an exclusive status by activating another exclusive
+ * status.  So, if @a status_id is an exclusive status and @a active is @c
+ * FALSE, this function does nothing.
  *
  * @param account   The account.
  * @param status_id The ID of the status.
- * @param active    The active state.
- * @param attrs		A list of attributes in key/value pairs
+ * @param active    Whether @a status_id is to be activated (<tt>TRUE</tt>) or
+ *                  deactivated (<tt>FALSE</tt>).
+ * @param attrs     A list of <tt>const char *</tt> attribute names followed by
+ *                  <tt>const char *</tt> attribute values for the status.
+ *                  (For example, one pair might be <tt>"message"</tt> followed
+ *                  by <tt>"hello, talk to me!"</tt>.)
  */
 void purple_account_set_status_list(PurpleAccount *account,
-								  const char *status_id,
-								  gboolean active, GList *attrs);
+	const char *status_id, gboolean active, GList *attrs);
 
 /**
  * Clears all protocol-specific settings on an account.
--- a/libpurple/accountopt.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/accountopt.h	Wed Jan 30 10:53:22 2008 +0000
@@ -83,7 +83,10 @@
 /*@{*/
 
 /**
- * Creates a new account option.
+ * Creates a new account option.  If you know what @a type will be in advance,
+ * consider using purple_account_option_bool_new(),
+ * purple_account_option_int_new(), purple_account_option_string_new() or
+ * purple_account_option_list_new() (as appropriate) instead.
  *
  * @param type      The type of option.
  * @param text      The text of the option.
@@ -91,8 +94,8 @@
  *
  * @return The account option.
  */
-PurpleAccountOption *purple_account_option_new(PurplePrefType type, const char *text,
-										   const char *pref_name);
+PurpleAccountOption *purple_account_option_new(PurplePrefType type,
+	const char *text, const char *pref_name);
 
 /**
  * Creates a new boolean account option.
@@ -104,8 +107,7 @@
  * @return The account option.
  */
 PurpleAccountOption *purple_account_option_bool_new(const char *text,
-												const char *pref_name,
-												gboolean default_value);
+	const char *pref_name, gboolean default_value);
 
 /**
  * Creates a new integer account option.
@@ -117,8 +119,7 @@
  * @return The account option.
  */
 PurpleAccountOption *purple_account_option_int_new(const char *text,
-											   const char *pref_name,
-											   int default_value);
+	const char *pref_name, int default_value);
 
 /**
  * Creates a new string account option.
@@ -130,8 +131,7 @@
  * @return The account option.
  */
 PurpleAccountOption *purple_account_option_string_new(const char *text,
-												  const char *pref_name,
-												  const char *default_value);
+	const char *pref_name, const char *default_value);
 
 /**
  * Creates a new list account option.
@@ -140,7 +140,7 @@
  * strings inside will be freed automatically.
  *
  * The list is a list of PurpleKeyValuePair items. The key is the ID stored and
- * used internally, and the value is the label displayed.
+ * used internally, and the <tt>(const char *)</tt> value is the label displayed.
  *
  * @param text      The text of the option.
  * @param pref_name The account preference name for the option.
@@ -149,8 +149,7 @@
  * @return The account option.
  */
 PurpleAccountOption *purple_account_option_list_new(const char *text,
-												const char *pref_name,
-												GList *list);
+	const char *pref_name, GList *list);
 
 /**
  * Destroys an account option.
@@ -240,11 +239,13 @@
 const char *purple_account_option_get_text(const PurpleAccountOption *option);
 
 /**
- * Returns the account setting for an account option.
+ * Returns the name of an account option.  This corresponds to the @c pref_name
+ * parameter supplied to purple_account_option_new() or one of the
+ * type-specific constructors.
  *
  * @param option The account option.
  *
- * @return The account setting.
+ * @return The option's name.
  */
 const char *purple_account_option_get_setting(const PurpleAccountOption *option);
 
--- a/libpurple/blist.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/blist.c	Wed Jan 30 10:53:22 2008 +0000
@@ -640,10 +640,10 @@
 
 		if (purple_account_is_connected(buddy->account))
 		{
-			int cmp;
-
-			cmp = purple_presence_compare(purple_buddy_get_presence(new_priority),
-			                            purple_buddy_get_presence(buddy));
+			int cmp = 1;
+			if (purple_account_is_connected(new_priority->account))
+				cmp = purple_presence_compare(purple_buddy_get_presence(new_priority),
+						purple_buddy_get_presence(buddy));
 
 			if (cmp > 0 || (cmp == 0 &&
 			                purple_prefs_get_bool("/purple/contact/last_match")))
@@ -753,6 +753,26 @@
 	return ret;
 }
 
+PurpleBlistNode *purple_blist_node_get_parent(PurpleBlistNode *node)
+{
+	return node ? node->parent : NULL;
+}
+
+PurpleBlistNode *purple_blist_node_get_first_child(PurpleBlistNode *node)
+{
+	return node ? node->child : NULL;
+}
+
+PurpleBlistNode *purple_blist_node_get_sibling_next(PurpleBlistNode *node)
+{
+	return node? node->next : NULL;
+}
+
+PurpleBlistNode *purple_blist_node_get_sibling_prev(PurpleBlistNode *node)
+{
+	return node? node->prev : NULL;
+}
+
 void
 purple_blist_update_buddy_status(PurpleBuddy *buddy, PurpleStatus *old_status)
 {
@@ -2232,6 +2252,22 @@
 	return (PurpleGroup *)(((PurpleBlistNode *)chat)->parent);
 }
 
+PurpleAccount *
+purple_chat_get_account(PurpleChat *chat)
+{
+	g_return_val_if_fail(chat != NULL, NULL);
+
+	return chat->account;
+}
+
+GHashTable *
+purple_chat_get_components(PurpleChat *chat)
+{
+	g_return_val_if_fail(chat != NULL, NULL);
+
+	return chat->components;
+}
+
 PurpleContact *purple_buddy_get_contact(PurpleBuddy *buddy)
 {
 	g_return_val_if_fail(buddy != NULL, NULL);
--- a/libpurple/blist.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/blist.h	Wed Jan 30 10:53:22 2008 +0000
@@ -53,13 +53,13 @@
 
 } PurpleBlistNodeType;
 
-#define PURPLE_BLIST_NODE_IS_CHAT(n)    ((n)->type == PURPLE_BLIST_CHAT_NODE)
-#define PURPLE_BLIST_NODE_IS_BUDDY(n)   ((n)->type == PURPLE_BLIST_BUDDY_NODE)
-#define PURPLE_BLIST_NODE_IS_CONTACT(n) ((n)->type == PURPLE_BLIST_CONTACT_NODE)
-#define PURPLE_BLIST_NODE_IS_GROUP(n)   ((n)->type == PURPLE_BLIST_GROUP_NODE)
+#define PURPLE_BLIST_NODE_IS_CHAT(n)    (purple_blist_node_get_type(n) == PURPLE_BLIST_CHAT_NODE)
+#define PURPLE_BLIST_NODE_IS_BUDDY(n)   (purple_blist_node_get_type(n) == PURPLE_BLIST_BUDDY_NODE)
+#define PURPLE_BLIST_NODE_IS_CONTACT(n) (purple_blist_node_get_type(n) == PURPLE_BLIST_CONTACT_NODE)
+#define PURPLE_BLIST_NODE_IS_GROUP(n)   (purple_blist_node_get_type(n) == PURPLE_BLIST_GROUP_NODE)
 
 #define PURPLE_BUDDY_IS_ONLINE(b) \
-	((b) != NULL && purple_account_is_connected((b)->account) && \
+	((b) != NULL && purple_account_is_connected(purple_buddy_get_account(b)) && \
 	 purple_presence_is_online(purple_buddy_get_presence(b)))
 
 typedef enum
@@ -231,10 +231,66 @@
  * @param node		A node.
  * @param offline	Whether to include nodes for offline accounts
  * @return	The next node
+ * @see purple_blist_node_get_parent
+ * @see purple_blist_node_get_first_child
+ * @see purple_blist_node_get_sibling_next
+ * @see purple_blist_node_get_sibling_prev
  */
 PurpleBlistNode *purple_blist_node_next(PurpleBlistNode *node, gboolean offline);
 
 /**
+ * Returns the parent node of a given node.
+ *
+ * @param node A node.
+ * @return  The parent node.
+ * @since 2.4.0
+ * @see purple_blist_node_get_first_child
+ * @see purple_blist_node_get_sibling_next
+ * @see purple_blist_node_get_sibling_prev
+ * @see purple_blist_node_next
+ */
+PurpleBlistNode *purple_blist_node_get_parent(PurpleBlistNode *node);
+
+/**
+ * Returns the the first child node of a given node.
+ *
+ * @param node A node.
+ * @return  The child node.
+ * @since 2.4.0
+ * @see purple_blist_node_get_parent
+ * @see purple_blist_node_get_sibling_next
+ * @see purple_blist_node_get_sibling_prev
+ * @see purple_blist_node_next
+ */
+PurpleBlistNode *purple_blist_node_get_first_child(PurpleBlistNode *node);
+
+/**
+ * Returns the sibling node of a given node.
+ *
+ * @param node A node.
+ * @return  The sibling node.
+ * @since 2.4.0
+ * @see purple_blist_node_get_parent
+ * @see purple_blist_node_get_first_child
+ * @see purple_blist_node_get_sibling_prev
+ * @see purple_blist_node_next
+ */
+PurpleBlistNode *purple_blist_node_get_sibling_next(PurpleBlistNode *node);
+
+/**
+ * Returns the previous sibling node of a given node.
+ *
+ * @param node A node.
+ * @return  The sibling node.
+ * @since 2.4.0
+ * @see purple_blist_node_get_parent
+ * @see purple_blist_node_get_first_child
+ * @see purple_blist_node_get_sibling_next
+ * @see purple_blist_node_next
+ */
+PurpleBlistNode *purple_blist_node_get_sibling_prev(PurpleBlistNode *node);
+
+/**
  * Shows the buddy list, creating a new one if necessary.
  */
 void purple_blist_show(void);
@@ -667,6 +723,26 @@
 PurpleGroup *purple_chat_get_group(PurpleChat *chat);
 
 /**
+ * Returns the account the chat belongs to.
+ *
+ * @param chat  The chat.
+ *
+ * @return  The account the chat belongs to.
+ * @since 2.4.0
+ */
+PurpleAccount *purple_chat_get_account(PurpleChat *chat);
+
+/**
+ * Get a hashtable containing information about a chat.
+ *
+ * @param chat  The chat.
+ *
+ * @constreturn  The hashtable.
+ * @since 2.4.0
+ */
+GHashTable *purple_chat_get_components(PurpleChat *chat);
+
+/**
  * Returns the group of which the buddy is a member.
  *
  * @param buddy   The buddy
--- a/libpurple/buddyicon.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/buddyicon.c	Wed Jan 30 10:53:22 2008 +0000
@@ -701,11 +701,6 @@
 	}
 	unref_filename(old_icon);
 
-	if (img)
-		g_hash_table_insert(pointer_icon_cache, account, img);
-	else
-		g_hash_table_remove(pointer_icon_cache, account);
-
 	if (purple_account_is_connected(account))
 	{
 		PurpleConnection *gc;
@@ -729,6 +724,11 @@
 	}
 	g_free(old_icon);
 
+	if (img)
+		g_hash_table_insert(pointer_icon_cache, account, img);
+	else
+		g_hash_table_remove(pointer_icon_cache, account);
+
 	return img;
 }
 
--- a/libpurple/certificate.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/certificate.c	Wed Jan 30 10:53:22 2008 +0000
@@ -568,7 +568,7 @@
 		_("Single-use Certificate Verification"),
 		primary,
 		secondary,
-		1,            /* Accept by default */
+		0,            /* Accept by default */
 		NULL,         /* No account */
 		NULL,         /* No other user */
 		NULL,         /* No associated conversation */
@@ -1199,7 +1199,7 @@
 		_("SSL Certificate Verification"),
 		primary,
 		reason,
-		2,            /* Accept by default */
+		0,            /* Accept by default */
 		NULL,         /* No account */
 		NULL,         /* No other user */
 		NULL,         /* No associated conversation */
--- a/libpurple/connection.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/connection.c	Wed Jan 30 10:53:22 2008 +0000
@@ -52,8 +52,19 @@
 	PurpleConnection *gc = data;
 	PurplePluginProtocolInfo *prpl_info = NULL;
 
-	if (gc != NULL && gc->prpl != NULL)
-		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
+	if (gc == NULL)
+		return TRUE;
+
+	/* Only send keep-alives if we haven't heard from the
+	 * server in a while.
+	 */
+	if ((time(NULL) - gc->last_received) < KEEPALIVE_INTERVAL)
+		return TRUE;
+
+	if (gc->prpl == NULL)
+		return TRUE;
+
+	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
 
 	if (prpl_info && prpl_info->keepalive)
 		prpl_info->keepalive(gc);
@@ -413,6 +424,14 @@
 	return gc->account;
 }
 
+PurplePlugin *
+purple_connection_get_prpl(const PurpleConnection *gc)
+{
+	g_return_val_if_fail(gc != NULL, NULL);
+
+	return gc->prpl;
+}
+
 const char *
 purple_connection_get_password(const PurpleConnection *gc)
 {
--- a/libpurple/connection.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/connection.h	Wed Jan 30 10:53:22 2008 +0000
@@ -251,6 +251,8 @@
 	gboolean wants_to_die;
 
 	guint disconnect_timeout;    /**< Timer used for nasty stack tricks  */
+	time_t last_received;        /**< When we last received a packet. Set by the
+					  prpl to avoid sending unneeded keepalives */
 };
 
 #ifdef __cplusplus
@@ -362,7 +364,7 @@
  * @return TRUE if the account is connected, otherwise returns FALSE.
  */
 #define PURPLE_CONNECTION_IS_CONNECTED(gc) \
-	(gc->state == PURPLE_CONNECTED)
+	(purple_connection_get_state(gc) == PURPLE_CONNECTED)
 
 /**
  * Returns the connection's account.
@@ -374,6 +376,16 @@
 PurpleAccount *purple_connection_get_account(const PurpleConnection *gc);
 
 /**
+ * Returns the protocol plugin managing a connection.
+ *
+ * @param gc The connection.
+ *
+ * @return The protocol plugin.
+ * @since 2.4.0
+ */
+PurplePlugin * purple_connection_get_prpl(const PurpleConnection *gc);
+
+/**
  * Returns the connection's password.
  *
  * @param gc The connection.
--- a/libpurple/dbus-analyze-functions.py	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/dbus-analyze-functions.py	Wed Jan 30 10:53:22 2008 +0000
@@ -525,9 +525,10 @@
             try:
                 self.processfunction(functiontext, paramtexts)
             except MyException:
-                sys.stderr.write(myline + "\n")
+#                sys.stderr.write(myline + "\n")
+                 pass
             except:
-                sys.stderr.write(myline + "\n")
+#                sys.stderr.write(myline + "\n")
                 raise
 
         self.flush()
@@ -586,7 +587,7 @@
 else:
     fprefix = ""
 
-sys.stderr.write("%s: Functions not exported:\n" % sys.argv[0])
+#sys.stderr.write("%s: Functions not exported:\n" % sys.argv[0])
 
 if "client" in options:
     bindings = ClientBindingSet(sys.stdin, fprefix,
--- a/libpurple/dbus-server.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/dbus-server.c	Wed Jan 30 10:53:22 2008 +0000
@@ -689,6 +689,7 @@
 		switch (purple_values[i]->type)
 		{
 		case PURPLE_TYPE_INT:
+		case PURPLE_TYPE_ENUM:
 			xint = my_arg(gint);
 			dbus_message_iter_append_basic(iter, DBUS_TYPE_INT32, &xint);
 			break;
@@ -729,7 +730,7 @@
 			if (id == 0 && val != NULL)
 				error = TRUE;      /* Some error happened. */
 			dbus_message_iter_append_basic(iter,
-					(sizeof(void *) == 4) ? DBUS_TYPE_UINT32 : DBUS_TYPE_UINT64, &id);
+					(sizeof(id) == sizeof(dbus_int32_t)) ? DBUS_TYPE_INT32 : DBUS_TYPE_INT64, &id);
 			break;
 		default: /* no conversion implemented */
 			g_return_val_if_reached(TRUE);
--- a/libpurple/dnsquery.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/dnsquery.c	Wed Jan 30 10:53:22 2008 +0000
@@ -159,6 +159,24 @@
 }
 #endif
 
+static void
+write_to_parent(int fd, const void *buf, size_t count)
+{
+	ssize_t written;
+
+	written = write(fd, buf, count);
+	if (written != count) {
+		if (written < 0)
+			fprintf(stderr, "dns[%d]: Error writing data to "
+					"parent: %s\n", getpid(), strerror(errno));
+		else
+			fprintf(stderr, "dns[%d]: Error: Tried to write %"
+					G_GSIZE_FORMAT " bytes to parent but instead "
+					"wrote %" G_GSIZE_FORMAT " bytes\n",
+					getpid(), count, written);
+	}
+}
+
 G_GNUC_NORETURN static void
 purple_dnsquery_resolver_run(int child_out, int child_in, gboolean show_debug)
 {
@@ -201,7 +219,8 @@
 		}
 		rc = read(child_in, &dns_params, sizeof(dns_params_t));
 		if (rc < 0) {
-			perror("read()");
+			fprintf(stderr, "dns[%d]: Error: Could not read dns_params: "
+					"%s\n", getpid(), strerror(errno));
 			break;
 		}
 		if (rc == 0) {
@@ -210,11 +229,13 @@
 			_exit(0);
 		}
 		if (dns_params.hostname[0] == '\0') {
-			printf("dns[%d]: hostname = \"\" (port = %d)!!!\n", getpid(), dns_params.port);
+			fprintf(stderr, "dns[%d]: Error: Parent requested resolution "
+					"of an empty hostname (port = %d)!!!\n", getpid(),
+					dns_params.port);
 			_exit(1);
 		}
 		/* Tell our parent that we read the data successfully */
-		write(child_out, &ch, sizeof(ch));
+		write_to_parent(child_out, &ch, sizeof(ch));
 
 		/* We have the hostname and port, now resolve the IP */
 
@@ -230,7 +251,7 @@
 		 */
 		hints.ai_socktype = SOCK_STREAM;
 		rc = getaddrinfo(dns_params.hostname, servname, &hints, &res);
-		write(child_out, &rc, sizeof(rc));
+		write_to_parent(child_out, &rc, sizeof(rc));
 		if (rc != 0) {
 			close(child_out);
 			if (show_debug)
@@ -242,17 +263,17 @@
 		tmp = res;
 		while (res) {
 			size_t ai_addrlen = res->ai_addrlen;
-			write(child_out, &ai_addrlen, sizeof(ai_addrlen));
-			write(child_out, res->ai_addr, res->ai_addrlen);
+			write_to_parent(child_out, &ai_addrlen, sizeof(ai_addrlen));
+			write_to_parent(child_out, res->ai_addr, res->ai_addrlen);
 			res = res->ai_next;
 		}
 		freeaddrinfo(tmp);
-		write(child_out, &zero, sizeof(zero));
+		write_to_parent(child_out, &zero, sizeof(zero));
 #else
 		if (!inet_aton(dns_params.hostname, &sin.sin_addr)) {
 			struct hostent *hp;
 			if (!(hp = gethostbyname(dns_params.hostname))) {
-				write(child_out, &h_errno, sizeof(int));
+				write_to_parent(child_out, &h_errno, sizeof(int));
 				close(child_out);
 				if (show_debug)
 					printf("DNS Error: %d\n", h_errno);
@@ -265,10 +286,10 @@
 			sin.sin_family = AF_INET;
 
 		sin.sin_port = htons(dns_params.port);
-		write(child_out, &zero, sizeof(zero));
-		write(child_out, &addrlen, sizeof(addrlen));
-		write(child_out, &sin, addrlen);
-		write(child_out, &zero, sizeof(zero));
+		write_to_parent(child_out, &zero, sizeof(zero));
+		write_to_parent(child_out, &addrlen, sizeof(addrlen));
+		write_to_parent(child_out, &sin, addrlen);
+		write_to_parent(child_out, &zero, sizeof(zero));
 #endif
 		dns_params.hostname[0] = '\0';
 	}
--- a/libpurple/dnssrv.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/dnssrv.c	Wed Jan 30 10:53:22 2008 +0000
@@ -200,12 +200,20 @@
 	PurpleSrvCallback cb = query_data->cb;
 	int status;
 
-	if (read(source, &size, sizeof(int)) > 0)
+	if (read(source, &size, sizeof(int)) == sizeof(int))
 	{
+		ssize_t red;
 		purple_debug_info("dnssrv","found %d SRV entries\n", size);
 		tmp = res = g_new0(PurpleSrvResponse, size);
 		for (i = 0; i < size; i++) {
-			read(source, tmp++, sizeof(PurpleSrvResponse));
+			red = read(source, tmp++, sizeof(PurpleSrvResponse));
+			if (red != sizeof(PurpleSrvResponse)) {
+				purple_debug_error("dnssrv","unable to read srv "
+						"response: %s\n", g_strerror(errno));
+				size = 0;
+				g_free(res);
+				res = NULL;
+			}
 		}
 	}
 	else
@@ -307,7 +315,7 @@
 
 	/* back to main thread */
 	/* Note: this should *not* be attached to query_data->handle - it will cause leakage */
-	g_idle_add(res_main_thread_cb, query_data);
+	purple_timeout_add(0, res_main_thread_cb, query_data);
 
 	g_thread_exit(NULL);
 	return NULL;
@@ -350,9 +358,12 @@
 	/* Child */
 	if (pid == 0)
 	{
+		g_free(query);
+
 		close(out[0]);
 		close(in[1]);
 		resolve(in[0], out[1]);
+		/* resolve() does not return */
 	}
 
 	close(out[1]);
@@ -399,7 +410,7 @@
 	 * Asynchronously call the callback since stuff may not expect
 	 * the callback to be called before this returns */
 	if (query_data->error_message != NULL)
-		query_data->handle = g_idle_add(res_main_thread_cb, query_data);
+		query_data->handle = purple_timeout_add(0, res_main_thread_cb, query_data);
 
 	return query_data;
 #endif
--- a/libpurple/example/Makefile.am	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/example/Makefile.am	Wed Jan 30 10:53:22 2008 +0000
@@ -17,7 +17,8 @@
 	-DLIBDIR=\"$(libdir)/purple-$(PURPLE_MAJOR_VERSION)/\" \
 	-DLOCALEDIR=\"$(datadir)/locale\" \
 	-DSYSCONFDIR=\"$(sysconfdir)\" \
-	-I$(top_srcdir)/libpurple/ \
+	-I$(top_builddir)/libpurple \
+	-I$(top_srcdir)/libpurple \
 	-I$(top_srcdir) \
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
--- a/libpurple/example/nullclient.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/example/nullclient.c	Wed Jan 30 10:53:22 2008 +0000
@@ -21,28 +21,11 @@
  *
  */
 
-/* XXX: we probably shouldn't include internal.h in examples */
-#include "internal.h"
-
-#include "account.h"
-#include "conversation.h"
-#include "core.h"
-#include "debug.h"
-#include "eventloop.h"
-#include "ft.h"
-#include "log.h"
-#include "notify.h"
-#include "prefs.h"
-#include "prpl.h"
-#include "pounce.h"
-#include "savedstatuses.h"
-#include "sound.h"
-#include "status.h"
-#include "util.h"
-#include "whiteboard.h"
+#include "purple.h"
 
 #include <glib.h>
 
+#include <signal.h>
 #include <string.h>
 #include <unistd.h>
 
@@ -268,6 +251,7 @@
 	GMainLoop *loop = g_main_loop_new(NULL, FALSE);
 	PurpleAccount *account;
 	PurpleSavedStatus *status;
+	char *res;
 
 	/* libpurple's built-in DNS resolution forks processes to perform
 	 * blocking lookups without blocking the main process.  It does not
@@ -290,12 +274,20 @@
 		}
 	}
 	printf("Select the protocol [0-%d]: ", i-1);
-	fgets(name, sizeof(name), stdin);
+	res = fgets(name, sizeof(name), stdin);
+	if (!res) {
+		fprintf(stderr, "Failed to gets protocol selection.");
+		abort();
+	}
 	sscanf(name, "%d", &num);
 	prpl = g_list_nth_data(names, num);
 
 	printf("Username: ");
-	fgets(name, sizeof(name), stdin);
+	res = fgets(name, sizeof(name), stdin);
+	if (!res) {
+		fprintf(stderr, "Failed to read user name.");
+		abort();
+	}
 	name[strlen(name) - 1] = 0;  /* strip the \n at the end */
 
 	/* Create the account */
--- a/libpurple/ft.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/ft.c	Wed Jan 30 10:53:22 2008 +0000
@@ -32,6 +32,7 @@
 #include "proxy.h"
 #include "request.h"
 #include "util.h"
+#include "debug.h"
 
 #define FT_INITIAL_BUFFER_SIZE 4096
 #define FT_MAX_BUFFER_SIZE     65535
@@ -668,6 +669,22 @@
 	return xfer->remote_port;
 }
 
+time_t
+purple_xfer_get_start_time(const PurpleXfer *xfer)
+{
+	g_return_val_if_fail(xfer != NULL, 0);
+
+	return xfer->start_time;
+}
+
+time_t
+purple_xfer_get_end_time(const PurpleXfer *xfer)
+{
+	g_return_val_if_fail(xfer != NULL, 0);
+
+	return xfer->end_time;
+}
+
 void
 purple_xfer_set_completed(PurpleXfer *xfer, gboolean completed)
 {
@@ -903,7 +920,12 @@
 	if (condition & PURPLE_INPUT_READ) {
 		r = purple_xfer_read(xfer, &buffer);
 		if (r > 0) {
-			fwrite(buffer, 1, r, xfer->dest_fp);
+			const size_t wc = fwrite(buffer, 1, r, xfer->dest_fp);
+			if (wc != r) {
+				purple_debug_error("filetransfer", "Unable to write whole buffer.\n");
+				purple_xfer_cancel_remote(xfer);
+				return;
+			}
 		} else if(r < 0) {
 			purple_xfer_cancel_remote(xfer);
 			return;
@@ -911,6 +933,7 @@
 	}
 
 	if (condition & PURPLE_INPUT_WRITE) {
+		size_t result;
 		size_t s = MIN(purple_xfer_get_bytes_remaining(xfer), xfer->current_buffer_size);
 
 		/* this is so the prpl can keep the connection open
@@ -925,7 +948,13 @@
 
 		buffer = g_malloc0(s);
 
-		fread(buffer, 1, s, xfer->dest_fp);
+		result = fread(buffer, 1, s, xfer->dest_fp);
+		if (result != s) {
+			purple_debug_error("filetransfer", "Unable to read whole buffer.\n");
+			purple_xfer_cancel_remote(xfer);
+			g_free(buffer);
+			return;
+		}
 
 		/* Write as much as we're allowed to. */
 		r = purple_xfer_write(xfer, buffer, s);
--- a/libpurple/ft.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/ft.h	Wed Jan 30 10:53:22 2008 +0000
@@ -358,6 +358,26 @@
 unsigned int purple_xfer_get_remote_port(const PurpleXfer *xfer);
 
 /**
+ * Returns the time the transfer of a file started.
+ *
+ * @param xfer  The file transfer.
+ *
+ * @return The time when the transfer started.
+ * @since 2.4.0
+ */
+time_t purple_xfer_get_start_time(const PurpleXfer *xfer);
+
+/**
+ * Returns the time the transfer of a file ended.
+ *
+ * @param xfer  The file transfer.
+ *
+ * @return The time when the transfer ended.
+ * @since 2.4.0
+ */
+time_t purple_xfer_get_end_time(const PurpleXfer *xfer);
+
+/**
  * Sets the completed state for the file transfer.
  *
  * @param xfer      The file transfer.
--- a/libpurple/idle.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/idle.c	Wed Jan 30 10:53:22 2008 +0000
@@ -24,6 +24,7 @@
 
 #include "connection.h"
 #include "debug.h"
+#include "eventloop.h"
 #include "idle.h"
 #include "log.h"
 #include "prefs.h"
@@ -333,7 +334,7 @@
 
 	/* Initialize the idleness asynchronously so it doesn't check idleness,
 	 * and potentially try to change the status before the UI is initialized */
-	g_idle_add(_do_purple_idle_touch_cb, NULL);
+	purple_timeout_add(0, _do_purple_idle_touch_cb, NULL);
 
 }
 
--- a/libpurple/log.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/log.c	Wed Jan 30 10:53:22 2008 +0000
@@ -1859,11 +1859,15 @@
 
 static char * old_logger_read (PurpleLog *log, PurpleLogReadFlags *flags)
 {
+	size_t result;
 	struct old_logger_data *data = log->logger_data;
-	FILE *file = g_fopen(purple_stringref_value(data->pathref), "rb");
+	const char *path = purple_stringref_value(data->pathref);
+	FILE *file = g_fopen(path, "rb");
 	char *read = g_malloc(data->length + 1);
 	fseek(file, data->offset, SEEK_SET);
-	fread(read, data->length, 1, file);
+	result = fread(read, data->length, 1, file);
+	if (result != 1)
+		purple_debug_error("log", "Unable to read from log file: %s\n", path);
 	fclose(file);
 	read[data->length] = '\0';
 	*flags = 0;
--- a/libpurple/plugins/offlinemsg.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/plugins/offlinemsg.c	Wed Jan 30 10:53:22 2008 +0000
@@ -159,7 +159,7 @@
 	
 		purple_request_action(handle, _("Offline Message"), ask,
 					_("You can edit/delete the pounce from the `Buddy Pounces' dialog"),
-					1,
+					0,
 					offline->account, offline->who, offline->conv,
 					offline, 2,
 					_("Yes"), record_pounce,
--- a/libpurple/plugins/tcl/tcl_cmds.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/plugins/tcl/tcl_cmds.c	Wed Jan 30 10:53:22 2008 +0000
@@ -25,6 +25,7 @@
 #include "internal.h"
 #include "conversation.h"
 #include "connection.h"
+#include "eventloop.h"
 #include "account.h"
 #include "server.h"
 #include "notify.h"
@@ -1778,7 +1779,7 @@
 	}
 	/* We can't unload immediately, but we can unload at the first 
 	 * known safe opportunity. */
-	g_idle_add(unload_self, (gpointer)plugin);
+	purple_timeout_add(0, unload_self, (gpointer)plugin);
 
 	return TCL_OK;
 }
--- a/libpurple/protocols/bonjour/bonjour.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Wed Jan 30 10:53:22 2008 +0000
@@ -673,11 +673,12 @@
 
 	/* Try to figure out a good host name to use */
 	/* TODO: Avoid 'localhost,' if possible */
-	if (gethostname(hostname, 255) != 0) {
+	if (gethostname(hostname, sizeof(hostname)) != 0) {
 		purple_debug_warning("bonjour", "Error when getting host name: %s.  Using \"localhost.\"\n",
 				g_strerror(errno));
 		strcpy(hostname, "localhost");
 	}
+	hostname[sizeof(hostname) - 1] = '\0';
 	default_hostname = g_strdup(hostname);
 }
 
--- a/libpurple/protocols/bonjour/jabber.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/bonjour/jabber.c	Wed Jan 30 10:53:22 2008 +0000
@@ -995,7 +995,7 @@
 			bb->conversation = NULL;
 	}
 
-	purple_timeout_add(0, _async_bonjour_jabber_close_conversation_cb, bconv);
+	bconv->close_timeout = purple_timeout_add(0, _async_bonjour_jabber_close_conversation_cb, bconv);
 }
 
 void
@@ -1054,6 +1054,9 @@
 		if (bconv->context != NULL)
 			bonjour_parser_setup(bconv);
 
+		if (bconv->close_timeout != 0)
+			purple_timeout_remove(bconv->close_timeout);
+
 		g_free(bconv->buddy_name);
 		g_free(bconv->ip);
 		g_free(bconv);
--- a/libpurple/protocols/bonjour/jabber.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/bonjour/jabber.h	Wed Jan 30 10:53:22 2008 +0000
@@ -47,6 +47,7 @@
 	gint socket;
 	guint rx_handler;
 	guint tx_handler;
+	guint close_timeout;
 	PurpleCircBuffer *tx_buf;
 	int sent_stream_start; /* 0 = Unsent, 1 = Partial, 2 = Complete */
 	gboolean recv_stream_start;
--- a/libpurple/protocols/gg/gg.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/gg/gg.c	Wed Jan 30 10:53:22 2008 +0000
@@ -1314,7 +1314,7 @@
 			_("Unable to read socket"));
 		return;
 	}
-
+	gc->last_received = time(NULL);
 	switch (ev->type) {
 		case GG_EVENT_NONE:
 			/* Nothing happened. */
--- a/libpurple/protocols/irc/dcc_send.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/irc/dcc_send.c	Wed Jan 30 10:53:22 2008 +0000
@@ -51,9 +51,14 @@
  */
 static void irc_dccsend_recv_ack(PurpleXfer *xfer, const guchar *data, size_t size) {
 	unsigned long l;
+	size_t result;
 
 	l = htonl(xfer->bytes_sent);
-	write(xfer->fd, &l, sizeof(l));
+	result = write(xfer->fd, &l, sizeof(l));
+	if (result != sizeof(l)) {
+		purple_debug_error("irc", "unable to send acknowledgement: %s\n", g_strerror(errno));
+		/* TODO: We should probably close the connection here or something. */
+	}
 }
 
 static void irc_dccsend_recv_init(PurpleXfer *xfer) {
--- a/libpurple/protocols/irc/irc.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/irc/irc.c	Wed Jan 30 10:53:22 2008 +0000
@@ -572,6 +572,7 @@
 {
 	char *cur, *end;
 
+	irc->account->gc->last_received = time(NULL);
 	irc->inbufused += len;
 	irc->inbuf[irc->inbufused] = '\0';
 
--- a/libpurple/protocols/jabber/auth.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/jabber/auth.c	Wed Jan 30 10:53:22 2008 +0000
@@ -325,7 +325,7 @@
 					purple_request_yes_no(js->gc, _("Plaintext Authentication"),
 							_("Plaintext Authentication"),
 							msg,
-							2, js->gc->account, NULL, NULL, js->gc->account,
+							1, js->gc->account, NULL, NULL, js->gc->account,
 							allow_cyrus_plaintext_auth,
 							disallow_plaintext_auth);
 					g_free(msg);
@@ -344,6 +344,7 @@
 					 * Doing otherwise means that simply compiling with SASL support renders the client unable to connect to servers
 					 * which would connect without issue otherwise. -evands
 					 */
+					js->auth_type = JABBER_AUTH_IQ_AUTH;
 					jabber_auth_start_old(js);
 					return;
 				}
@@ -527,7 +528,7 @@
 			purple_request_yes_no(js->gc, _("Plaintext Authentication"),
 					_("Plaintext Authentication"),
 					msg,
-					2,
+					1,
 					purple_connection_get_account(js->gc), NULL, NULL,
 					purple_connection_get_account(js->gc), allow_plaintext_auth,
 					disallow_plaintext_auth);
@@ -719,7 +720,7 @@
 				purple_request_yes_no(js->gc, _("Plaintext Authentication"),
 						_("Plaintext Authentication"),
 						_("This server requires plaintext authentication over an unencrypted connection.  Allow this and continue authentication?"),
-						2,
+						1,
 						purple_connection_get_account(js->gc), NULL, NULL,
 						purple_connection_get_account(js->gc), allow_plaintext_auth,
 						disallow_plaintext_auth);
--- a/libpurple/protocols/jabber/jabber.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Wed Jan 30 10:53:22 2008 +0000
@@ -433,6 +433,7 @@
 	}
 
 	while((len = purple_ssl_read(gsc, buf, sizeof(buf) - 1)) > 0) {
+		gc->last_received = time(NULL);
 		buf[len] = '\0';
 		purple_debug(PURPLE_DEBUG_INFO, "jabber", "Recv (ssl)(%d): %s\n", len, buf);
 		jabber_parser_process(js, buf, len);
@@ -460,6 +461,7 @@
 		return;
 
 	if((len = read(js->fd, buf, sizeof(buf) - 1)) > 0) {
+		gc->last_received = time(NULL);
 #ifdef HAVE_CYRUS_SASL
 		if (js->sasl_maxbuf>0) {
 			const char *out;
@@ -2302,14 +2304,10 @@
 GList *jabber_attention_types(PurpleAccount *account)
 {
 	static GList *types = NULL;
-	PurpleAttentionType *attn;
 
 	if (!types) {
-		attn = g_new0(PurpleAttentionType, 1);
-		attn->name = _("Buzz");
-		attn->incoming_description = _("%s has buzzed you!");
-		attn->outgoing_description = _("Buzzing %s...");
-		types = g_list_append(types, attn);
+		types = g_list_append(types, purple_attention_type_new("Buzz", _("Buzz"),
+				_("%s has buzzed you!"), _("Buzzing %s...")));
 	}
 
 	return types;
--- a/libpurple/protocols/jabber/si.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/jabber/si.c	Wed Jan 30 10:53:22 2008 +0000
@@ -1100,7 +1100,7 @@
 		} else {
 			/* we've got multiple resources, we need to pick one to send to */
 			GList *l;
-			char *msg = g_strdup_printf(_("Please select which resource of %s you would like to send a file to"), xfer->who);
+			char *msg = g_strdup_printf(_("Please select the resource of %s to which you would like to send a file"), xfer->who);
 			PurpleRequestFields *fields = purple_request_fields_new();
 			PurpleRequestField *field = purple_request_field_choice_new("resource", _("Resource"), 0);
 			PurpleRequestFieldGroup *group = purple_request_field_group_new(NULL);
--- a/libpurple/protocols/msn/msn.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/msn/msn.c	Wed Jan 30 10:53:22 2008 +0000
@@ -122,15 +122,11 @@
 static GList *
 msn_attention_types(PurpleAccount *account)
 {
-	PurpleAttentionType *attn;
 	static GList *list = NULL;
 
 	if (!list) {
-		attn = g_new0(PurpleAttentionType, 1);
-		attn->name = _("Nudge");
-		attn->incoming_description = _("%s has nudged you!");
-		attn->outgoing_description = _("Nudging %s...");
-		list = g_list_append(list, attn);
+		list = g_list_append(list, purple_attention_type_new("Nudge", _("Nudge"),
+				_("%s has nudged you!"), _("Nudging %s...")));
 	}
 
 	return list;
@@ -374,7 +370,7 @@
 						_("Do you want to allow or disallow people on "
 						  "your buddy list to send you MSN Mobile pages "
 						  "to your cell phone or other mobile device?"),
-						-1,
+						PURPLE_DEFAULT_ACTION_NONE,
 						purple_connection_get_account(gc), NULL, NULL,
 						gc, 3,
 						_("Allow"), G_CALLBACK(enable_msn_pages_cb),
--- a/libpurple/protocols/msn/servconn.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/msn/servconn.c	Wed Jan 30 10:53:22 2008 +0000
@@ -391,6 +391,7 @@
 	session = servconn->session;
 
 	len = read(servconn->fd, buf, sizeof(buf) - 1);
+	servconn->session->account->gc->last_received = time(NULL);
 
 	if (len <= 0) {
 		switch (errno) {
--- a/libpurple/protocols/msnp9/msn.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/msnp9/msn.c	Wed Jan 30 10:53:22 2008 +0000
@@ -27,6 +27,7 @@
 
 #include "msn.h"
 #include "accountopt.h"
+#include "eventloop.h"
 #include "msg.h"
 #include "page.h"
 #include "pluginpref.h"
@@ -122,15 +123,11 @@
 static GList *
 msn_attention_types(PurpleAccount *account)
 {
-	PurpleAttentionType *attn;
 	static GList *list = NULL;
 
 	if (!list) {
-		attn = g_new0(PurpleAttentionType, 1);
-		attn->name = _("Nudge");
-		attn->incoming_description = _("%s has nudged you!");
-		attn->outgoing_description = _("Nudging %s...");
-		list = g_list_append(list, attn);
+		list = g_list_append(list, purple_attention_type_new("Nudge", _("Nudge"),
+				_("%s has nudged you!"), _("Nudging %s...")));
 	}
 
 	return list;
@@ -351,7 +348,7 @@
 						_("Do you want to allow or disallow people on "
 						  "your buddy list to send you MSN Mobile pages "
 						  "to your cell phone or other mobile device?"),
-						-1,
+						PURPLE_DEFAULT_ACTION_NONE,
 						purple_connection_get_account(gc), NULL, NULL,
 						gc, 3,
 						_("Allow"), G_CALLBACK(enable_msn_pages_cb),
@@ -860,7 +857,7 @@
 		imdata->msg = body_str;
 		imdata->flags = flags;
 		imdata->when = time(NULL);
-		g_idle_add(msn_send_me_im, imdata);
+		purple_timeout_add(0, msn_send_me_im, imdata);
 	}
 
 	msn_message_destroy(msg);
--- a/libpurple/protocols/msnp9/notification.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/msnp9/notification.c	Wed Jan 30 10:53:22 2008 +0000
@@ -1012,7 +1012,7 @@
 	{
 		purple_debug_error("msn",
 						 "Error opening temp passport file: %s\n",
-						 strerror(errno));
+						 g_strerror(errno));
 	}
 	else
 	{
@@ -1061,7 +1061,7 @@
 		{
 			purple_debug_error("msn",
 							 "Error closing temp passport file: %s\n",
-							 strerror(errno));
+							 g_strerror(errno));
 
 			g_unlink(session->passport_info.file);
 			g_free(session->passport_info.file);
--- a/libpurple/protocols/msnp9/servconn.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/msnp9/servconn.c	Wed Jan 30 10:53:22 2008 +0000
@@ -387,12 +387,13 @@
 	session = servconn->session;
 
 	len = read(servconn->fd, buf, sizeof(buf) - 1);
+	servconn->session->account->gc->last_received = time(NULL);
 
 	if (len < 0 && errno == EAGAIN)
 		return;
 	else if (len <= 0)
 	{
-		purple_debug_error("msn", "servconn read error, len: %d error: %s\n", len, strerror(errno));
+		purple_debug_error("msn", "servconn read error, len: %d error: %s\n", len, g_strerror(errno));
 		msn_servconn_got_error(servconn, MSN_SERVCONN_ERROR_READ);
 
 		return;
--- a/libpurple/protocols/myspace/myspace.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Wed Jan 30 10:53:22 2008 +0000
@@ -133,7 +133,7 @@
 
 	types = NULL;
 
-    /* Statuses are almost all the same. Define a macro to reduce code repetition. */
+	/* Statuses are almost all the same. Define a macro to reduce code repetition. */
 #define _MSIM_ADD_NEW_STATUS(prim) status =                         \
 	purple_status_type_new_with_attrs(                          \
 	prim,   /* PurpleStatusPrimitive */                         \
@@ -552,10 +552,8 @@
 		 * return 1 even if the message could not be sent, since I don't know if
 		 * it has failed yet--because the IM is only sent after the userid is
 		 * retrieved from the server (which happens after this function returns).
+		 * If an error does occur, it should be logged to the IM window.
 		 */
-		/* TODO: maybe if message is delayed, don't echo to conv window,
-		 * but do echo it to conv window manually once it is actually
-		 * sent? Would be complicated. */
 		rc = 1;
 	} else {
 		rc = -1;
@@ -563,19 +561,6 @@
 
 	g_free(message_msim);
 
-	/*
-	 * In MySpace, you login with your email address, but don't talk to other
-	 * users using their email address. So there is currently an asymmetry in the 
-	 * IM windows when using this plugin:
-	 *
-	 * you@example.com: hello
-	 * some_other_user: what's going on?
-	 * you@example.com: just coding a prpl
-	 *
-	 * TODO: Make the sent IM's appear as from the user's username, instead of
-	 * their email address. Purple uses the login (in MSIM, the email)--change this.
-	 */
-
 	return rc;
 }
 
@@ -602,7 +587,7 @@
 	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
 	g_return_val_if_fail(who != NULL, FALSE);
 	g_return_val_if_fail(text != NULL, FALSE);
-   
+
 	from_username = session->account->username;
 
 	g_return_val_if_fail(from_username != NULL, FALSE);
@@ -611,7 +596,7 @@
 				  type, from_username, who, text);
 
 	msg = msim_msg_new(
-            "bm", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(type),
+			"bm", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(type),
 			"sesskey", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(session->sesskey),
 			/* 't' will be inserted here */
 			"cv", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(MSIM_CLIENT_VERSION),
@@ -664,7 +649,7 @@
 msim_incoming_bm(MsimSession *session, MsimMessage *msg)
 {
 	guint bm;
-   
+
 	bm = msim_msg_get_integer(msg, "bm");
 
 	msim_incoming_bm_record_cv(session, msg);
@@ -745,7 +730,10 @@
 
 	/* TODO: dump unknown msgs to file, so user can send them to me
 	 * if they wish, to help add support for new messages (inspired
-	 * by Alexandr Shutko, who maintains OSCAR protocol documentation). */
+	 * by Alexandr Shutko, who maintains OSCAR protocol documentation). 
+	 *
+	 * Filed enhancement ticket for libpurple as #4688.
+	 */
 
 	purple_debug_info("msim", "Unrecognized data on account for %s\n", 
 			(session && session->account && session->account->username) ? 
@@ -787,9 +775,6 @@
 			msg_text, username);
 
 	if (g_str_equal(msg_text, "%typing%")) {
-		/* TODO: find out if msim repeatedly sends typing messages, so we can 
-		 * give it a timeout. Right now, there does seem to be an inordinately 
-		 * amount of time between typing stopped-typing notifications. */
 		serv_got_typing(session->gc, username, 0, PURPLE_TYPING);
 		rc = TRUE;
 	} else if (g_str_equal(msg_text, "%stoptyping%")) {
@@ -797,6 +782,25 @@
 		rc = TRUE;
 	} else if (strstr(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_")) {
 		rc = msim_incoming_zap(session, msg);
+	} else if (strstr(msg_text, "!!!GroupCount=")) {
+		/* TODO: support group chats. I think the number in msg_text has
+		 * something to do with the 'gid' field. */
+		purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text);
+
+		rc = TRUE;
+	} else if (strstr(msg_text, "!!!Offline=")) {
+		/* TODO: support group chats. This one might mean a user
+		 * went offline or exited the chat. */
+		purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text);
+
+		rc = TRUE;
+	} else if (msim_msg_get_integer(msg, "aid") != 0) {
+		purple_debug_info("msim", "TODO: implement #4691, group chat from %d on %d: %s\n",
+				msim_msg_get_integer(msg, "aid"),
+				msim_msg_get_integer(msg, "f"),
+				msg_text);
+
+		rc = TRUE;
 	} else {
 		msim_unrecognized(session, msg, 
 				"got to msim_incoming_action but unrecognized value for 'msg'");
@@ -959,6 +963,7 @@
 
 	if (!user) {
 		/* User isn't on blist, create a temporary user to store info. */
+		/* TODO: is this legit, or is it somehow responsible for #3444? */
 		PurpleBuddy *buddy;
 
 		user = g_new0(MsimUser, 1);
@@ -1052,6 +1057,7 @@
 	guint status_code;
 	const gchar *message;
 	gchar *stripped;
+	gchar *unrecognized_msg;
 
 	session = (MsimSession *)account->gc->proto_data;
 
@@ -1083,6 +1089,12 @@
 			purple_debug_info("msim", "msim_set_status: unknown "
 					"status interpreting as online");
 			status_code = MSIM_STATUS_CODE_ONLINE;
+
+			unrecognized_msg = g_strdup_printf("msim_set_status, unrecognized status type: %d\n", 
+					purple_status_type_get_primitive(type));
+			msim_unrecognized(session, NULL, unrecognized_msg);
+			g_free(unrecognized_msg);
+
 			break;
 	}
 
@@ -1197,13 +1209,13 @@
 	/* Special elements name beginning with '_', we'll use internally within the
 	 * program (did not come directly from the wire). */
 	msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, username); /* This makes 'msg' the owner of 'username' */
-  
+
 	/* TODO: attach more useful information, like ImageURL */
 
 	msim_process(session, msg);
 
 	/* TODO: Free copy cloned from  msim_preprocess_incoming(). */
-	//XXX msim_msg_free(msg);
+	/* msim_msg_free(msg); */
 	msim_msg_free(body);
 }
 
@@ -1211,16 +1223,17 @@
  *
  * @param wanted_uid
  *
- * @return Username of wanted_uid, if on blist, or NULL. Static string. 
+ * @return Username of wanted_uid, if on blist, or NULL. 
+ * 	This is a static string, so don't free it. Copy it if needed.
  *
  */
 static const gchar *
-msim_uid2username_from_blist(MsimSession *session, guint wanted_uid)
+msim_uid2username_from_blist(PurpleAccount *account, guint wanted_uid)
 {
 	GSList *buddies, *cur;
-	gchar *ret;
-
-	buddies = purple_find_buddies(session->account, NULL); 
+	const gchar *ret;
+
+	buddies = purple_find_buddies(account, NULL); 
 
 	if (!buddies)
 	{
@@ -1244,7 +1257,7 @@
 
 		if (uid == wanted_uid)
 		{
-			ret = g_strdup(name);
+			ret = name;
 			break;
 		}
 	}
@@ -1271,7 +1284,7 @@
 		/* 'f' = userid message is from, in buddy messages */
 		uid = msim_msg_get_integer(msg, "f");
 
-		username = msim_uid2username_from_blist(session, uid); 
+		username = msim_uid2username_from_blist(session->account, uid); 
 
 		if (username) {
 			/* Know username already, use it. */
@@ -1313,13 +1326,13 @@
 	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
 
 	delta = time(NULL) - session->last_comm;
-	//purple_debug_info("msim", "msim_check_alive: delta=%d\n", delta);
+	/* purple_debug_info("msim", "msim_check_alive: delta=%d\n", delta); */
 	if (delta >= MSIM_KEEPALIVE_INTERVAL) {
 		errmsg = g_strdup_printf(_("Connection to server lost (no data received within %d seconds)"), (int)delta);
 
 		purple_debug_info("msim", "msim_check_alive: %s > interval of %d, presumed dead\n",
 				errmsg, MSIM_KEEPALIVE_INTERVAL);
-		purple_connection_error_reason (session->gc,
+		purple_connection_error_reason(session->gc,
 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, errmsg);
 
 		purple_notify_error(session->gc, NULL, errmsg, NULL);
@@ -1741,7 +1754,7 @@
 static gboolean
 msim_web_challenge(MsimSession *session, MsimMessage *msg)
 {
-	/* TODO: web challenge, store token */
+	/* TODO: web challenge, store token. #2659. */
 	return FALSE;
 }
 
@@ -1832,13 +1845,14 @@
 	if (msim_msg_get(msg, "fatal")) {
 		PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
 		purple_debug_info("msim", "fatal error, closing\n");
+
 		switch (err) {
-			case 260: /* Incorrect password */
+			case MSIM_ERROR_INCORRECT_PASSWORD: /* Incorrect password */
 				reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
 				if (!purple_account_get_remember_password(session->account))
 					purple_account_set_password(session->account, NULL);
 				break;
-			case 6: /* Logged in elsewhere */
+			case MSIM_ERROR_LOGGED_IN_ELSEWHERE: /* Logged in elsewhere */
 				reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE;
 				if (!purple_account_get_remember_password(session->account))
 					purple_account_set_password(session->account, NULL);
@@ -1871,6 +1885,7 @@
 	gchar *status_headline, *status_headline_escaped;
 	gint status_code, purple_status_code;
 	gchar *username;
+	gchar *unrecognized_msg;
 
 	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
 	g_return_val_if_fail(msg != NULL, FALSE);
@@ -1903,7 +1918,8 @@
 
 	blist = purple_get_blist();
 
-	/* Add buddy if not found */
+	/* Add buddy if not found.
+	 * TODO: Could this be responsible for #3444? */
 	user = msim_find_user(session, username);
 	if (!user) {
 		PurpleBuddy *buddy;
@@ -1915,7 +1931,7 @@
 
 		user = msim_get_user_from_buddy(buddy);
 
-		/* All buddies on list should have 'uid' integer associated with them. */
+		/* All buddies on list should have a UserID integer associated with them. */
 		purple_blist_node_set_int(&buddy->node, "UserID", msim_msg_get_integer(msg, "f"));
 		
 		msim_store_user_info(session, msg, NULL);
@@ -1938,7 +1954,7 @@
 
 	/* don't copy; let the MsimUser own the headline, memory-wise */
 	user->headline = status_headline_escaped;
-  
+
 	/* Set user status */
 	switch (status_code) {
 		case MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN: 
@@ -1959,9 +1975,15 @@
 			break;
 
 		default:
-			purple_debug_info("msim", "msim_status for %s, unknown status code %d, treating as available\n",
+			purple_debug_info("msim", "msim_incoming_status for %s, unknown status code %d, treating as available\n",
 						username, status_code);
 			purple_status_code = PURPLE_STATUS_AVAILABLE;
+
+			unrecognized_msg = g_strdup_printf("msim_incoming_status, unrecognized status code: %d\n", 
+					status_code);
+			msim_unrecognized(session, NULL, unrecognized_msg);
+			g_free(unrecognized_msg);
+
 	}
 
 	purple_prpl_got_user_status(session->account, username, purple_primitive_get_id_from_type(purple_status_code), NULL);
@@ -2156,10 +2178,15 @@
 		gchar *msg;
 
 		msg = g_strdup_printf(_("No such user: %s"), username);
-		purple_notify_error(NULL, NULL, _("User lookup"), msg);
+		if (!purple_conv_present_error(username, session->account, msg)) { 
+			purple_notify_error(NULL, NULL, _("User lookup"), msg); 
+		}
+
 		g_free(msg);
 		g_free(username);
-		//msim_msg_free(msg);
+		/* TODO: free
+		 * msim_msg_free(msg);
+		 */
 		return;
 	}
 
@@ -2180,7 +2207,9 @@
 	g_free(uid_field_name);
 	g_free(uid_before);
 	g_free(username);
-	//msim_msg_free(msg);
+	/* TODO: free 
+	 * msim_msg_free(msg);
+	 */
 }
 
 /** Postprocess and send a message.
@@ -2222,8 +2251,7 @@
 			uid = 0;
 		}
 
-		if (!buddy || !uid)
-		{
+		if (!buddy || !uid) {
 			/* Don't have uid offhand - need to ask for it, and wait until hear back before sending. */
 			purple_debug_info("msim", ">>> msim_postprocess_outgoing: couldn't find username %s in blist\n",
 					username ? username : "(NULL)");
@@ -2244,7 +2272,9 @@
 	
 	rc = msim_msg_send(session, msg);
 
-	//msim_msg_free(msg);
+	/* TODO: free
+	 * msim_msg_free(msg);
+	 */
 
 	return rc;
 }
@@ -2304,7 +2334,7 @@
 			"blocklist", MSIM_TYPE_BOOLEAN, TRUE,
 			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
 			/* TODO: MsimMessage lists. Currently <uid> isn't replaced in lists. */
-			//"idlist", MSIM_TYPE_STRING, g_strdup("a-|<uid>|b-|<uid>"),
+			/* "idlist", MSIM_TYPE_STRING, g_strdup("a-|<uid>|b-|<uid>"), */
 			"idlist", MSIM_TYPE_LIST, blocklist_updates,
 			NULL);
 
@@ -2316,6 +2346,72 @@
 	msim_msg_free(blocklist_msg);
 }
 
+/**
+ * Returns a string of a username in canonical form. Basically removes all the
+ * spaces, lowercases the string, and looks up user IDs to usernames.
+ * Normalizing tom, TOM, Tom, and 6221 wil all return 'tom'.
+ *
+ * Borrowed this code from oscar_normalize. Added checking for 
+ * "if userid, get name before normalizing"
+ */
+const char *msim_normalize(const PurpleAccount *account, const char *str) {
+	static char normalized[BUF_LEN];
+	char *tmp1, *tmp2;
+	int i, j;
+	guint id;
+
+	g_return_val_if_fail(str != NULL, NULL);
+
+	if (msim_is_userid(str)) {
+		/* Have user ID, we need to get their username first :) */
+		const char *username;
+
+		/* If the account does not exist, we can't look up the user. */
+		if (!account)
+			return str;
+
+		id = atol(str);
+		username = msim_uid2username_from_blist((PurpleAccount *)account, id);
+		if (!username) {
+			/* Not in buddy list... scheisse... TODO: Manual Lookup! Bug #4631 */
+			/* Note: manual lookup using msim_lookup_user() is a problem inside 
+			 * msim_normalize(), because msim_lookup_user() calls a callback function
+			 * when the user information has been looked up, but msim_normalize() expects
+			 * the result immediately. */
+			strncpy(normalized, str, BUF_LEN);
+		} else {
+			strncpy(normalized, username, BUF_LEN);
+		}
+	} else {
+		/* Have username. */
+		strncpy(normalized, str, BUF_LEN);
+	}
+
+	/* Strip spaces. */
+	for (i=0, j=0; normalized[j]; i++, j++) {
+		while (normalized[j] == ' ')
+			j++;
+		normalized[i] = normalized[j];
+	}
+	normalized[i] = '\0';
+
+	/* Lowercase and perform UTF-8 normalization. */
+	tmp1 = g_utf8_strdown(normalized, -1);
+	tmp2 = g_utf8_normalize(tmp1, -1, G_NORMALIZE_DEFAULT);
+	g_snprintf(normalized, sizeof(normalized), "%s", tmp2);
+	g_free(tmp2);
+	g_free(tmp1);
+
+	/* TODO: re-add caps and spacing back to what the user wanted.
+	 * User can format their own names, for example 'msimprpl' is shown
+	 * as 'MsIm PrPl' in the official client.
+	 *
+	 * TODO: file a ticket to add this enhancement.
+	 */
+
+	return normalized;
+}
+
 /** Return whether the buddy can be messaged while offline.
  *
  * The protocol supports offline messages in just the same way as online
@@ -2364,31 +2460,30 @@
 	g_return_if_fail(cond == PURPLE_INPUT_READ);
 	g_return_if_fail(MSIM_SESSION_VALID(session));
 
-	/* Mark down that we got data, so don't timeout. */
+	/* Mark down that we got data, so we don't timeout. */
 	session->last_comm = time(NULL);
 
-	/* Only can handle so much data at once... 
-	 * If this happens, try recompiling with a higher MSIM_READ_BUF_SIZE.
-	 * Should be large enough to hold the largest protocol message.
-	 */
-	if (session->rxoff >= MSIM_READ_BUF_SIZE) {
-		purple_debug_error("msim", 
-				"msim_input_cb: %d-byte read buffer full! rxoff=%d\n",
-				MSIM_READ_BUF_SIZE, session->rxoff);
-		purple_connection_error_reason (gc,
-				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-				_("Read buffer full"));
+	/* If approaching end of buffer, reallocate some more memory. */
+	if (session->rxsize < session->rxoff + MSIM_READ_BUF_SIZE) {
+		purple_debug_info("msim", 
+			"msim_input_cb: %d-byte read buffer full, rxoff=%d, " "growing by %d bytes\n",
+			session->rxsize, session->rxoff, MSIM_READ_BUF_SIZE);
+			session->rxsize += MSIM_READ_BUF_SIZE;
+			session->rxbuf = g_realloc(session->rxbuf, session->rxsize);
+		
 		return;
 	}
 
-	purple_debug_info("msim", "buffer at %d (max %d), reading up to %d\n",
-			session->rxoff, MSIM_READ_BUF_SIZE, 
-			MSIM_READ_BUF_SIZE - session->rxoff);
+	purple_debug_info("msim", "dynamic buffer at %d (max %d), reading up to %d\n",
+			session->rxoff, session->rxsize,
+			MSIM_READ_BUF_SIZE - session->rxoff - 1);
 
 	/* Read into buffer. On Win32, need recv() not read(). session->fd also holds
 	 * the file descriptor, but it sometimes differs from the 'source' parameter.
 	 */
-	n = recv(session->fd, session->rxbuf + session->rxoff, MSIM_READ_BUF_SIZE - session->rxoff, 0);
+	n = recv(session->fd, 
+		 session->rxbuf + session->rxoff, 
+		 session->rxsize - session->rxoff - 1, 0);
 
 	if (n < 0 && errno == EAGAIN) {
 		return;
@@ -2408,13 +2503,13 @@
 		return;
 	}
 
-	if (n + session->rxoff >= MSIM_READ_BUF_SIZE) {
+	if (n + session->rxoff > session->rxsize) {
 		purple_debug_info("msim_input_cb", "received %d bytes, pushing rxoff to %d, over buffer size of %d\n",
-				n, n + session->rxoff, MSIM_READ_BUF_SIZE);
-		/* TODO: g_realloc like msn, yahoo, irc, jabber? */
+				n, n + session->rxoff, session->rxsize);
 		purple_connection_error_reason (gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-			_("Read buffer full"));
+			_("Read buffer full (2)"));
+		return;
 	}
 
 	/* Null terminate */
@@ -2457,6 +2552,7 @@
 			purple_connection_error_reason (gc,
 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 				_("Unparseable message"));
+			break;
 		} else {
 			/* Process message and then free it (processing function should
 			 * clone message if it wants to keep it afterwards.) */
@@ -2469,10 +2565,11 @@
 		/* Move remaining part of buffer to beginning. */
 		session->rxoff -= strlen(session->rxbuf) + strlen(MSIM_FINAL_STRING);
 		memmove(session->rxbuf, end + strlen(MSIM_FINAL_STRING), 
-				MSIM_READ_BUF_SIZE - (end + strlen(MSIM_FINAL_STRING) - session->rxbuf));
-
-		/* Clear end of buffer */
-		//memset(end, 0, MSIM_READ_BUF_SIZE - (end - session->rxbuf));
+				session->rxsize - (end + strlen(MSIM_FINAL_STRING) - session->rxbuf));
+
+		/* Clear end of buffer 
+		 * memset(end, 0, MSIM_READ_BUF_SIZE - (end - session->rxbuf));
+		 */
 	}
 }
 
@@ -2675,7 +2772,7 @@
 	uid = msim_msg_get_integer(contact_info, "ContactID");
 
 	if (!user_lookup_info) {
-		username = g_strdup(msim_uid2username_from_blist(session, uid));
+		username = g_strdup(msim_uid2username_from_blist(session->account, uid));
 		g_return_if_fail(username != NULL);
 	} else {
 		user_lookup_info_body = msim_msg_get_dictionary(user_lookup_info, "body");
@@ -2724,8 +2821,9 @@
 	msim_store_user_info(session, contact_info, NULL);
 
 	/* TODO: other fields, store in 'user' */
-
 	msim_msg_free(contact_info);
+
+	g_free(username);
 }
 
 /** Add first ContactID in contact_info to buddy's list. Used to add
@@ -2743,7 +2841,7 @@
 	g_return_val_if_fail(uid != 0, FALSE);
 
 	/* Lookup the username, since NickName and IMName is unreliable */
-	username = msim_uid2username_from_blist(session, uid);
+	username = msim_uid2username_from_blist(session->account, uid);
 	if (!username) {
 		gchar *uid_str;
 
@@ -2772,7 +2870,10 @@
 	msim_msg_dump("msim_got_contact_list: reply=%s", reply);
 
 	body = msim_msg_get_dictionary(reply, "body");
-	g_return_if_fail(body != NULL);
+	if (!body) {
+		/* No friends. Not an error. */
+		return;
+	}
 
 	buddy_count = 0;
 
@@ -2970,7 +3071,7 @@
 	NULL,              /* rename_group */
 	NULL,              /* buddy_free */
 	NULL,              /* convo_closed */
-	NULL,              /* normalize */
+	msim_normalize,    /* normalize */
 	NULL,              /* set_buddy_icon */
 	NULL,              /* remove_group */
 	NULL,              /* get_cb_real_name */
--- a/libpurple/protocols/myspace/myspace.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/myspace/myspace.h	Wed Jan 30 10:53:22 2008 +0000
@@ -180,7 +180,9 @@
 #define MSIM_CONTACT_LIST_IMPORT_ALL_FRIENDS	1
 #define MSIM_CONTACT_LIST_IMPORT_TOP_FRIENDS	2
 
-#define MsimAttentionType PurpleAttentionType
+/* Error codes */
+#define MSIM_ERROR_INCORRECT_PASSWORD           260
+#define MSIM_ERROR_LOGGED_IN_ELSEWHERE          6
 
 /* Functions */
 gboolean msim_load(PurplePlugin *plugin);
@@ -201,6 +203,8 @@
 void msim_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group);
 void msim_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group);
 
+const char *msim_normalize(const PurpleAccount *account, const char *str);
+
 gboolean msim_offline_message(const PurpleBuddy *buddy);
 
 void msim_close(PurpleConnection *gc);
--- a/libpurple/protocols/myspace/session.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/myspace/session.c	Wed Jan 30 10:53:22 2008 +0000
@@ -59,7 +59,8 @@
 	session->server_info = NULL;
 
 	session->rxoff = 0;
-	session->rxbuf = g_new0(gchar, MSIM_READ_BUF_SIZE);
+	session->rxsize = MSIM_READ_BUF_SIZE;
+	session->rxbuf = g_new0(gchar, session->rxsize);
 	session->next_rid = 1;
 	session->last_comm = time(NULL);
 	session->inbox_status = 0;
--- a/libpurple/protocols/myspace/session.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/myspace/session.h	Wed Jan 30 10:53:22 2008 +0000
@@ -42,6 +42,7 @@
 
 	gchar *rxbuf;                       /**< Receive buffer */
 	guint rxoff;                        /**< Receive buffer offset */
+	guint rxsize;                       /**< Receive buffer size */
 	guint next_rid;                     /**< Next request/response ID */
 	time_t last_comm;                   /**< Time received last communication */
 	guint inbox_status;                 /**< Bit field of inbox notifications */
--- a/libpurple/protocols/myspace/zap.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/myspace/zap.c	Wed Jan 30 10:53:22 2008 +0000
@@ -29,15 +29,12 @@
 msim_attention_types(PurpleAccount *acct)
 {
 	static GList *types = NULL;
-	MsimAttentionType* attn;
+	PurpleAttentionType* attn;
 
 	if (!types) {
-#define _MSIM_ADD_NEW_ATTENTION(icn, nme, incoming, outgoing)              \
-		attn = g_new0(MsimAttentionType, 1);                       \
-		attn->icon_name = icn;                                     \
-		attn->name = nme;                                          \
-		attn->incoming_description = incoming;                     \
-		attn->outgoing_description = outgoing;                     \
+#define _MSIM_ADD_NEW_ATTENTION(icn, ulname, nme, incoming, outgoing) \
+		attn = purple_attention_type_new(ulname, nme, incoming, outgoing); \
+		purple_attention_type_set_icon_name(attn, icn); \
 		types = g_list_append(types, attn);
 
 		/* TODO: icons for each zap */
@@ -48,37 +45,46 @@
 		 * projectile or weapon."  This term often has an electrical
 		 * connotation, for example, "he was zapped by electricity when
 		 * he put a fork in the toaster." */
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("Zap"), _("%s has zapped you!"), _("Zapping %s..."));
+		_MSIM_ADD_NEW_ATTENTION(NULL, "Zap", _("Zap"), _("%s has zapped you!"),
+				_("Zapping %s..."));
 
 		/* Whack means "to hit or strike someone with a sharp blow" */
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("Whack"), _("%s has whacked you!"), _("Whacking %s..."));
+		_MSIM_ADD_NEW_ATTENTION(NULL, "Whack", _("Whack"),
+				_("%s has whacked you!"), _("Whacking %s..."));
 
 		/* Torch means "to set on fire."  Don't worry, this doesn't
 		 * make a whole lot of sense in English, either.  Feel free
 		 * to translate it literally. */
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("Torch"), _("%s has torched you!"), _("Torching %s..."));
+		_MSIM_ADD_NEW_ATTENTION(NULL, "Torch", _("Torch"),
+				_("%s has torched you!"), _("Torching %s..."));
 
 		/* Smooch means "to kiss someone, often enthusiastically" */
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("Smooch"), _("%s has smooched you!"), _("Smooching %s..."));
+		_MSIM_ADD_NEW_ATTENTION(NULL, "Smooch", _("Smooch"),
+				_("%s has smooched you!"), _("Smooching %s..."));
 
 		/* A hug is a display of affection; wrapping your arms around someone */
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("Hug"), _("%s has hugged you!"), _("Hugging %s..."));
+		_MSIM_ADD_NEW_ATTENTION(NULL, "Hug", _("Hug"), _("%s has hugged you!"),
+				_("Hugging %s..."));
 
 		/* Slap means "to hit someone with an open/flat hand" */
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("Slap"), _("%s has slapped you!"), _("Slapping %s..."));
+		_MSIM_ADD_NEW_ATTENTION(NULL, "Slap", _("Slap"),
+				_("%s has slapped you!"), _("Slapping %s..."));
 
 		/* Goose means "to pinch someone on their butt" */
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("Goose"), _("%s has goosed you!"), _("Goosing %s..."));
+		_MSIM_ADD_NEW_ATTENTION(NULL, "Goose", _("Goose"),
+				_("%s has goosed you!"), _("Goosing %s..."));
 
 		/* A high-five is when two people's hands slap each other
 		 * in the air above their heads.  It is done to celebrate
 		 * something, often a victory, or to congratulate someone. */
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("High-five"), _("%s has high-fived you!"), _("High-fiving %s..."));
+		_MSIM_ADD_NEW_ATTENTION(NULL, "High-five", _("High-five"),
+				_("%s has high-fived you!"), _("High-fiving %s..."));
 
 		/* We're not entirely sure what the MySpace people mean by
 		 * this... but we think it's the equivalent of "prank."  Or, for
 		 * someone to perform a mischievous trick or practical joke. */
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("Punk"), _("%s has punk'd you!"), _("Punking %s..."));
+		_MSIM_ADD_NEW_ATTENTION(NULL, "Punk", _("Punk"),
+				_("%s has punk'd you!"), _("Punking %s..."));
 
 		/* Raspberry is a slang term for the vibrating sound made
 		 * when you stick your tongue out of your mouth with your
@@ -87,7 +93,8 @@
 		 * gesture, so it does not carry a harsh negative
 		 * connotation.  It is generally used in a playful tone
 		 * with friends. */
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("Raspberry"), _("%s has raspberried you!"), _("Raspberrying %s..."));
+		_MSIM_ADD_NEW_ATTENTION(NULL, "Raspberry", _("Raspberry"),
+				_("%s has raspberried you!"), _("Raspberrying %s..."));
 	}
 
 	return types;
@@ -99,14 +106,14 @@
 {
 	GList *types;
 	MsimSession *session;
-	MsimAttentionType *attn;
+	PurpleAttentionType *attn;
 	PurpleBuddy *buddy;
 
 	session = (MsimSession *)gc->proto_data;
 
 	/* Look for this attention type, by the code index given. */
 	types = msim_attention_types(gc->account);
-	attn = (MsimAttentionType *)g_list_nth_data(types, code);
+	attn = (PurpleAttentionType *)g_list_nth_data(types, code);
 
 	if (!attn) {
 		purple_debug_info("msim_send_attention", "got invalid zap code %d\n", code);
@@ -200,12 +207,12 @@
 	i = 0;
 	do
 	{
-		MsimAttentionType *attn;
+		PurpleAttentionType *attn;
 
-		attn = (MsimAttentionType *)types->data;
+		attn = (PurpleAttentionType *)types->data;
 
-		act = purple_menu_action_new(attn->name, PURPLE_CALLBACK(msim_send_zap_from_menu),
-				GUINT_TO_POINTER(i), NULL);
+		act = purple_menu_action_new(purple_attention_type_get_name(attn),
+				PURPLE_CALLBACK(msim_send_zap_from_menu), GUINT_TO_POINTER(i), NULL);
 		zap_menu = g_list_append(zap_menu, act);
 
 		++i;
--- a/libpurple/protocols/novell/nmfield.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/novell/nmfield.c	Wed Jan 30 10:53:22 2008 +0000
@@ -164,9 +164,7 @@
 		case NMFIELD_TYPE_BINARY:
 		case NMFIELD_TYPE_UTF8:
 		case NMFIELD_TYPE_DN:
-			if (field->ptr_value != NULL) {
-				g_free(field->ptr_value);
-			}
+			g_free(field->ptr_value);
 			break;
 
 		case NMFIELD_TYPE_ARRAY:
--- a/libpurple/protocols/novell/novell.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/novell/novell.c	Wed Jan 30 10:53:22 2008 +0000
@@ -1920,6 +1920,7 @@
 	parms = g_slist_append(parms, nm_event_get_conference(event));
 
 	/* Prompt the user */
+	/* TODO: Would it be better to use serv_got_chat_invite() here? */
 	gc = purple_account_get_connection(user->client_data);
 	purple_request_action(gc, title, primary, secondary,
 						PURPLE_DEFAULT_ACTION_NONE,
--- a/libpurple/protocols/oscar/family_buddy.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/oscar/family_buddy.c	Wed Jan 30 10:53:22 2008 +0000
@@ -219,8 +219,9 @@
 	if ((userfunc = aim_callhandler(od, snac->family, snac->subtype)))
 		ret = userfunc(od, conn, frame, &userinfo);
 
-	if (snac->subtype == SNAC_SUBTYPE_BUDDY_ONCOMING)
-		aim_locate_requestuserinfo(od, userinfo.sn);
+	if (snac->subtype == SNAC_SUBTYPE_BUDDY_ONCOMING && userinfo.flags & AIM_FLAG_AWAY)
+		aim_locate_autofetch_away_message(od, userinfo.sn);
+
 	aim_info_free(&userinfo);
 
 	return ret;
--- a/libpurple/protocols/oscar/family_locate.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/oscar/family_locate.c	Wed Jan 30 10:53:22 2008 +0000
@@ -386,11 +386,11 @@
 }
 
 void
-aim_locate_requestuserinfo(OscarData *od, const char *sn)
+aim_locate_autofetch_away_message(OscarData *od, const char *sn)
 {
 	struct userinfo_node *cur;
 
-	/* Make sure we haven't already requested info for this buddy */
+	/* Make sure we haven't already made an info request for this buddy */
 	for (cur = od->locate.requested; cur != NULL; cur = cur->next)
 		if (aim_sncmp(sn, cur->sn) == 0)
 			return;
@@ -401,7 +401,7 @@
 	cur->next = od->locate.requested;
 	od->locate.requested = cur;
 
-	aim_locate_getinfoshort(od, cur->sn, 0x00000003);
+	aim_locate_getinfoshort(od, cur->sn, 0x00000002);
 }
 
 aim_userinfo_t *aim_locate_finduserinfo(OscarData *od, const char *sn) {
--- a/libpurple/protocols/oscar/flap_connection.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/oscar/flap_connection.c	Wed Jan 30 10:53:22 2008 +0000
@@ -360,7 +360,7 @@
 
 	conn = data;
 	od = conn->od;
-	account = (PURPLE_CONNECTION_IS_VALID(od->gc) ? purple_connection_get_account(od->gc) : NULL);
+	account = purple_connection_get_account(od->gc);
 
 	purple_debug_info("oscar", "Destroying oscar connection of "
 			"type 0x%04hx.  Disconnect reason is %d\n",
@@ -375,8 +375,8 @@
 	 * TODO: If we don't have a SNAC_FAMILY_LOCATE connection then
 	 * we should try to request one instead of disconnecting.
 	 */
-	if (account && !account->disconnecting &&
-		((od->oscar_connections == NULL) || (!flap_connection_getbytype(od, SNAC_FAMILY_LOCATE))))
+	if (!account->disconnecting && ((od->oscar_connections == NULL)
+			|| (!flap_connection_getbytype(od, SNAC_FAMILY_LOCATE))))
 	{
 		/* No more FLAP connections!  Sign off this PurpleConnection! */
 		gchar *tmp;
@@ -817,6 +817,7 @@
 						OSCAR_DISCONNECT_LOST_CONNECTION, g_strerror(errno));
 				break;
 			}
+			conn->od->gc->last_received = time(NULL);
 
 			/* If we don't even have a complete FLAP header then do nothing */
 			conn->header_received += read;
--- a/libpurple/protocols/oscar/libaim.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/oscar/libaim.c	Wed Jan 30 10:53:22 2008 +0000
@@ -92,11 +92,11 @@
 	NULL,					/* whiteboard_prpl_ops */
 	NULL,					/* send_raw */
 	NULL,					/* roomlist_room_serialize */
+	NULL,					/* unregister_user */
+	NULL,					/* send_attention */
+	NULL,					/* get_attention_types */
 
 	/* padding */
-	NULL,
-	NULL,
-	NULL,
 	NULL
 };
 
--- a/libpurple/protocols/oscar/libicq.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/oscar/libicq.c	Wed Jan 30 10:53:22 2008 +0000
@@ -92,11 +92,11 @@
 	NULL,					/* whiteboard_prpl_ops */
 	NULL,					/* send_raw */
 	NULL,					/* roomlist_room_serialize */
+	NULL,					/* unregister_user */
+	NULL,					/* send_attention */
+	NULL,					/* get_attention_types */
 
 	/* padding */
-	NULL,
-	NULL,
-	NULL,
 	NULL
 };
 
--- a/libpurple/protocols/oscar/oscar.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Wed Jan 30 10:53:22 2008 +0000
@@ -197,7 +197,6 @@
 
 static void purple_icons_fetch(PurpleConnection *gc);
 
-static void recent_buddies_cb(const char *name, PurplePrefType type, gconstpointer value, gpointer data);
 void oscar_set_info(PurpleConnection *gc, const char *info);
 static void oscar_set_info_and_status(PurpleAccount *account, gboolean setinfo, const char *rawinfo, gboolean setstatus, PurpleStatus *status);
 static void oscar_set_extendedstatus(PurpleConnection *gc);
@@ -1203,6 +1202,48 @@
 	return 1;
 }
 
+static void
+idle_reporting_pref_cb(const char *name, PurplePrefType type,
+		gconstpointer value, gpointer data)
+{
+	PurpleConnection *gc;
+	OscarData *od;
+	gboolean report_idle;
+	guint32 presence;
+
+	gc = data;
+	od = gc->proto_data;
+	report_idle = strcmp((const char *)value, "none") != 0;
+	presence = aim_ssi_getpresence(od->ssi.local);
+
+	if (report_idle)
+		aim_ssi_setpresence(od, presence | 0x400);
+	else
+		aim_ssi_setpresence(od, presence & ~0x400);
+}
+
+/**
+ * Should probably make a "Use recent buddies group" account preference
+ * so that this option is surfaced to the user.
+ */
+static void
+recent_buddies_pref_cb(const char *name, PurplePrefType type,
+		gconstpointer value, gpointer data)
+{
+	PurpleConnection *gc;
+	OscarData *od;
+	guint32 presence;
+
+	gc = data;
+	od = gc->proto_data;
+	presence = aim_ssi_getpresence(od->ssi.local);
+
+	if (value)
+		aim_ssi_setpresence(od, presence & ~AIM_SSI_PRESENCE_FLAG_NORECENTBUDDIES);
+	else
+		aim_ssi_setpresence(od, presence | AIM_SSI_PRESENCE_FLAG_NORECENTBUDDIES);
+}
+
 void
 oscar_login(PurpleAccount *account)
 {
@@ -1293,7 +1334,8 @@
 	}
 
 	/* Connect to core Purple signals */
-	purple_prefs_connect_callback(gc, "/plugins/prpl/oscar/recent_buddies", recent_buddies_cb, gc);
+	purple_prefs_connect_callback(gc, "/purple/away/idle_reporting", idle_reporting_pref_cb, gc);
+	purple_prefs_connect_callback(gc, "/plugins/prpl/oscar/recent_buddies", recent_buddies_pref_cb, gc);
 
 	newconn = flap_connection_new(od, SNAC_FAMILY_AUTH);
 	newconn->connect_data = purple_proxy_connect(NULL, account,
@@ -1538,6 +1580,7 @@
 {
 	struct pieceofcrap *pos = data;
 	gchar *buf;
+	ssize_t result;
 
 	if (!PURPLE_CONNECTION_IS_VALID(pos->gc))
 	{
@@ -1549,8 +1592,8 @@
 	pos->fd = source;
 
 	if (source < 0) {
-		buf = g_strdup_printf(_("You may be disconnected shortly.  You may want to use TOC until "
-			"this is fixed.  Check %s for updates."), PURPLE_WEBSITE);
+		buf = g_strdup_printf(_("You may be disconnected shortly.  "
+				"Check %s for updates."), PURPLE_WEBSITE);
 		purple_notify_warning(pos->gc, NULL,
 							_("Unable to get a valid AIM login hash."),
 							buf);
@@ -1562,7 +1605,18 @@
 
 	buf = g_strdup_printf("GET " AIMHASHDATA "?offset=%ld&len=%ld&modname=%s HTTP/1.0\n\n",
 			pos->offset, pos->len, pos->modname ? pos->modname : "");
-	write(pos->fd, buf, strlen(buf));
+	result = send(pos->fd, buf, strlen(buf), 0);
+	if (result != strlen(buf)) {
+		if (result < 0)
+			purple_debug_error("oscar", "Error writing %" G_GSIZE_FORMAT
+					" bytes to fetch AIM hash data: %s\n",
+					strlen(buf), g_strerror(errno));
+		else
+			purple_debug_error("oscar", "Tried to write %"
+					G_GSIZE_FORMAT " bytes to fetch AIM hash data but "
+					"instead wrote %" G_GSIZE_FORMAT " bytes\n",
+					strlen(buf), result);
+	}
 	g_free(buf);
 	g_free(pos->modname);
 	pos->inpa = purple_input_add(pos->fd, PURPLE_INPUT_READ, damn_you, pos);
@@ -1744,7 +1798,6 @@
 	int type = 0;
 	gboolean buddy_is_away = FALSE;
 	const char *status_id;
-	char *itmsurl = NULL;
 	va_list ap;
 	aim_userinfo_t *info;
 
@@ -1792,11 +1845,6 @@
 			status_id = OSCAR_STATUS_ID_AVAILABLE;
 	}
 
-	if (info->itmsurl_encoding && info->itmsurl && info->itmsurl_len)
-		/* Grab the iTunes Music Store URL */
-		itmsurl = oscar_encoding_to_utf8(account, info->itmsurl_encoding,
-				info->itmsurl, info->itmsurl_len);
-
 	if (info->flags & AIM_FLAG_WIRELESS)
 	{
 		purple_prpl_got_user_status(account, info->sn, OSCAR_STATUS_ID_MOBILE, NULL);
@@ -1804,27 +1852,31 @@
 		purple_prpl_got_user_status_deactive(account, info->sn, OSCAR_STATUS_ID_MOBILE);
 	}
 
-	if (status_id == OSCAR_STATUS_ID_AVAILABLE)
+	if (strcmp(status_id, OSCAR_STATUS_ID_AVAILABLE) == 0)
 	{
 		char *message = NULL;
+		char *itmsurl = NULL;
 
 		if (info->status != NULL && info->status[0] != '\0')
 			/* Grab the available message */
 			message = oscar_encoding_to_utf8(account, info->status_encoding,
 					info->status, info->status_len);
 
+		if (info->itmsurl_encoding && info->itmsurl && info->itmsurl_len)
+			/* Grab the iTunes Music Store URL */
+			itmsurl = oscar_encoding_to_utf8(account, info->itmsurl_encoding,
+					info->itmsurl, info->itmsurl_len);
+
 		purple_prpl_got_user_status(account, info->sn, status_id,
 				"message", message, "itmsurl", itmsurl, NULL);
 
 		g_free(message);
+		g_free(itmsurl);
 	}
 	else
 	{
-		purple_prpl_got_user_status(account, info->sn, status_id,
-				"itmsurl", itmsurl, NULL);
-	}
-
-	g_free(itmsurl);
+		purple_prpl_got_user_status(account, info->sn, status_id, NULL);
+	}
 
 	/* Login time stuff */
 	if (info->present & AIM_USERINFO_PRESENT_ONLINESINCE)
@@ -4471,7 +4523,6 @@
 	PurplePresence *presence;
 	PurpleStatusType *status_type;
 	PurpleStatusPrimitive primitive;
-	gboolean invisible;
 
 	char *htmlinfo;
 	char *info_encoding = NULL;
@@ -4486,7 +4537,6 @@
 	status_type = purple_status_get_type(status);
 	primitive = purple_status_type_get_primitive(status_type);
 	presence = purple_account_get_presence(account);
-	invisible = purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_INVISIBLE);
 
 	if (!setinfo)
 	{
@@ -4924,9 +4974,21 @@
 			}
 		}
 		/* Presence settings (idle time visibility) */
-		if ((tmp = aim_ssi_getpresence(od->ssi.local)) != 0xFFFFFFFF)
-			if (!(tmp & 0x400))
+		tmp = aim_ssi_getpresence(od->ssi.local);
+		if (tmp != 0xFFFFFFFF) {
+			const char *idle_reporting_pref;
+			gboolean report_idle;
+
+			idle_reporting_pref = purple_prefs_get_string("/purple/away/idle_reporting");
+			report_idle = strcmp(idle_reporting_pref, "none") != 0;
+
+			if (report_idle)
 				aim_ssi_setpresence(od, tmp | 0x400);
+			else
+				aim_ssi_setpresence(od, tmp & ~0x400);
+		}
+
+
 	} /* end pruning buddies from local list */
 
 	/* Add from server list to local list */
@@ -6002,7 +6064,7 @@
 			_("Because this reveals your IP address, it "
 			  "may be considered a security risk.  Do you "
 			  "wish to continue?"),
-			0,
+			0, /* Default action is "connect" */
 			purple_connection_get_account(gc), data->who, NULL,
 			data, 2,
 			_("C_onnect"), G_CALLBACK(oscar_ask_directim_yes_cb),
@@ -6565,33 +6627,6 @@
 	}
 }
 
-static void
-recent_buddies_cb(const char *name, PurplePrefType type,
-				  gconstpointer value, gpointer data)
-{
-	PurpleConnection *gc = data;
-	OscarData *od = gc->proto_data;
-	guint32 presence;
-
-	presence = aim_ssi_getpresence(od->ssi.local);
-
-	if (value) {
-		presence &= ~AIM_SSI_PRESENCE_FLAG_NORECENTBUDDIES;
-		aim_ssi_setpresence(od, presence);
-	} else {
-		presence |= AIM_SSI_PRESENCE_FLAG_NORECENTBUDDIES;
-		aim_ssi_setpresence(od, presence);
-	}
-}
-
-#ifdef USE_PRPL_PREFERENCES
-	ppref = purple_plugin_pref_new_with_name_and_label("/plugins/prpl/oscar/recent_buddies", _("Use recent buddies group"));
-	purple_plugin_pref_frame_add(frame, ppref);
-
-	ppref = purple_plugin_pref_new_with_name_and_label("/plugins/prpl/oscar/show_idle", _("Show how long you have been idle"));
-	purple_plugin_pref_frame_add(frame, ppref);
-#endif
-
 const char *
 oscar_normalize(const PurpleAccount *account, const char *str)
 {
@@ -6754,7 +6789,7 @@
 	/* Preferences */
 	purple_prefs_add_none("/plugins/prpl/oscar");
 	purple_prefs_add_bool("/plugins/prpl/oscar/recent_buddies", FALSE);
-	purple_prefs_add_bool("/plugins/prpl/oscar/show_idle", FALSE);
+	purple_prefs_remove("/plugins/prpl/oscar/show_idle");
 	purple_prefs_remove("/plugins/prpl/oscar/always_use_rv_proxy");
 
 	/* protocol handler */
--- a/libpurple/protocols/oscar/oscar.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Wed Jan 30 10:53:22 2008 +0000
@@ -1074,7 +1074,7 @@
 /* 0x000f */ int aim_locate_setinterests(OscarData *od, const char *interest1, const char *interest2, const char *interest3, const char *interest4, const char *interest5, guint16 privacy);
 /* 0x0015 */ int aim_locate_getinfoshort(OscarData *od, const char *sn, guint32 flags);
 
-void aim_locate_requestuserinfo(OscarData *od, const char *sn);
+void aim_locate_autofetch_away_message(OscarData *od, const char *sn);
 guint32 aim_locate_getcaps(OscarData *od, ByteStream *bs, int len);
 guint32 aim_locate_getcaps_short(OscarData *od, ByteStream *bs, int len);
 void aim_info_free(aim_userinfo_t *);
--- a/libpurple/protocols/oscar/peer.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/oscar/peer.c	Wed Jan 30 10:53:22 2008 +0000
@@ -1041,7 +1041,7 @@
 						  "Images.  Because your IP address will be "
 						  "revealed, this may be considered a privacy "
 						  "risk."),
-						PURPLE_DEFAULT_ACTION_NONE,
+						0, /* Default action is "connect" */
 						account, sn, NULL,
 						conn, 2,
 						_("C_onnect"), G_CALLBACK(peer_connection_got_proposition_yes_cb),
--- a/libpurple/protocols/qq/file_trans.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/qq/file_trans.c	Wed Jan 30 10:53:22 2008 +0000
@@ -97,6 +97,7 @@
 	guint8 *buffer;
 	PurpleCipher *cipher;
 	PurpleCipherContext *context;
+	size_t wc;
 
 	const gint QQ_MAX_FILE_MD5_LENGTH = 10002432;
 
@@ -109,15 +110,20 @@
 
 	buffer = g_newa(guint8, filelen);
 	g_return_if_fail(buffer != NULL);
-	fread(buffer, filelen, 1, fp);
+	wc = fread(buffer, filelen, 1, fp);
+	fclose(fp);
+	if (wc != 1) {
+		purple_debug_error("qq", "Unable to read file: %s\n", filename);
+
+		/* TODO: XXX: Really, the caller should be modified to deal with this properly. */
+		return;
+	}
 
 	cipher = purple_ciphers_find_cipher("md5");
 	context = purple_cipher_context_new(cipher, NULL);
 	purple_cipher_context_append(context, buffer, filelen);
 	purple_cipher_context_digest(context, 16, md5, NULL);
 	purple_cipher_context_destroy(context);
-
-	fclose(fp);
 }
 
 static void _qq_get_file_header(guint8 *buf, guint8 **cursor, gint buflen, qq_file_header *fh)
--- a/libpurple/protocols/qq/group_im.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/qq/group_im.c	Wed Jan 30 10:53:22 2008 +0000
@@ -135,7 +135,7 @@
 
 	purple_request_action(gc, _("QQ Qun Operation"),
 			    msg, reason,
-			    2,
+			    PURPLE_DEFAULT_ACTION_NONE,
 				purple_connection_get_account(gc), nombre, NULL,
 				g, 3,
 			    _("Approve"),
--- a/libpurple/protocols/qq/group_opt.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/qq/group_opt.c	Wed Jan 30 10:53:22 2008 +0000
@@ -96,7 +96,8 @@
 	g_return_if_fail(g != NULL && g->gc != NULL && g->member > 0);
 
 	qq_send_packet_get_info(g->gc, g->member, TRUE);	/* we want to see window */
-	purple_request_action(g->gc, NULL, _("Do you want to approve the request?"), "", 2,
+	purple_request_action(g->gc, NULL, _("Do you want to approve the request?"), "",
+					PURPLE_DEFAULT_ACTION_NONE,
 					purple_connection_get_account(g->gc), NULL, NULL,
 					g, 2,
 					_("Reject"), G_CALLBACK(qq_group_reject_application_with_struct),
--- a/libpurple/protocols/qq/sys_msg.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/qq/sys_msg.c	Wed Jan 30 10:53:22 2008 +0000
@@ -84,7 +84,8 @@
 
 	nombre = uid_to_purple_name(uid);
 	purple_request_action
-	    (gc, NULL, _("Do you want to approve the request?"), "", 2,
+	    (gc, NULL, _("Do you want to approve the request?"), "",
+		PURPLE_DEFAULT_ACTION_NONE,
 		 purple_connection_get_account(gc), nombre, NULL,
 		 g, 2,
 	     _("Reject"), G_CALLBACK(qq_reject_add_request_with_gc_and_uid),
@@ -107,7 +108,8 @@
 	qq_send_packet_get_info(gc, uid, TRUE);	/* we want to see window */
 	nombre = uid_to_purple_name(uid);
 	purple_request_action
-	    (gc, NULL, _("Do you want to add this buddy?"), "", 2,
+	    (gc, NULL, _("Do you want to add this buddy?"), "",
+		PURPLE_DEFAULT_ACTION_NONE,
 		 purple_connection_get_account(gc), nombre, NULL,
 		 g, 2,
 	     _("Cancel"), NULL,
@@ -166,7 +168,8 @@
 		message = g_strdup_printf(_("You have been added by %s"), from);
 		_qq_sys_msg_log_write(gc, message, from);
 		purple_request_action(gc, NULL, message,
-				    _("Would you like to add him?"), 2,
+				    _("Would you like to add him?"),
+					PURPLE_DEFAULT_ACTION_NONE,
 					purple_connection_get_account(gc), name, NULL,
 					g, 3,
 				    _("Cancel"), NULL,
@@ -240,7 +243,7 @@
 	_qq_sys_msg_log_write(gc, message, from);
 
 	purple_request_action
-	    (gc, NULL, message, reason, 2,
+	    (gc, NULL, message, reason, PURPLE_DEFAULT_ACTION_NONE,
 		purple_connection_get_account(gc), name, NULL,
 		 g, 3,
 	     _("Reject"),
@@ -260,7 +263,7 @@
 		g2->uid = strtol(from, NULL, 10);
 		message = g_strdup_printf(_("%s is not in your buddy list"), from);
 		purple_request_action(gc, NULL, message,
-				    _("Would you like to add him?"), 2,
+				    _("Would you like to add him?"), PURPLE_DEFAULT_ACTION_NONE,
 					purple_connection_get_account(gc), name, NULL,
 					g2, 3,
 					_("Cancel"), NULL,
--- a/libpurple/protocols/sametime/sametime.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/sametime/sametime.c	Wed Jan 30 10:53:22 2008 +0000
@@ -1695,7 +1695,9 @@
   int len;
 
   len = read(sock, buf, BUF_LEN);
-  if(len > 0) mwSession_recv(session, buf, len);
+  if(len > 0) {
+    mwSession_recv(session, buf, len);
+  }
 
   return len;
 }
@@ -2286,6 +2288,7 @@
 
   PurpleXfer *xfer;
   FILE *fp;
+  size_t wc;
 
   xfer = mwFileTransfer_getClientData(ft);
   g_return_if_fail(xfer != NULL);
@@ -2294,7 +2297,12 @@
   g_return_if_fail(fp != NULL);
 
   /* we must collect and save our precious data */
-  fwrite(data->data, 1, data->len, fp);
+  wc = fwrite(data->data, 1, data->len, fp);
+  if (wc != data->len) {
+    DEBUG_ERROR("failed to write data\n");
+    purple_xfer_cancel_local(xfer);
+    return;
+  }
 
   /* update the progress */
   xfer->bytes_sent += data->len;
--- a/libpurple/protocols/simple/simple.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/simple/simple.c	Wed Jan 30 10:53:22 2008 +0000
@@ -1356,10 +1356,21 @@
 }
 
 static gboolean process_publish_response(struct simple_account_data *sip, struct sipmsg *msg, struct transaction *tc) {
+
+	const gchar *etag = NULL;
+
 	if(msg->response != 200 && msg->response != 408) {
 		/* never send again */
 		sip->republish = -1;
 	}
+
+	etag = sipmsg_find_header(msg, "SIP-Etag");
+	if (etag) {
+		/* we must store the etag somewhere. */
+		g_free(sip->publish_etag);
+		sip->publish_etag = g_strdup(etag);
+	}
+
 	return TRUE;
 }
 
@@ -1368,10 +1379,12 @@
 	gchar *uri = g_strdup_printf("sip:%s@%s", sip->username, sip->servername);
 	gchar *doc = gen_pidf(sip, TRUE);
 
-	add_headers = g_strdup_printf("%s%d%s",
-		"Expires: ",
-		PUBLISH_EXPIRATION,
-		"\r\nEvent: presence\r\n"
+	add_headers = g_strdup_printf("%s%s%s%s%d\r\n%s",
+		sip->publish_etag ? "SIP-If-Match: " : "",
+		sip->publish_etag ? sip->publish_etag : "",
+		sip->publish_etag ? "\r\n" : "",
+		"Expires: ", PUBLISH_EXPIRATION,
+		"Event: presence\r\n"
 		"Content-Type: application/pidf+xml\r\n");
 
 	send_sip_request(sip->gc, "PUBLISH", uri, uri,
@@ -1384,14 +1397,23 @@
 
 static void send_closed_publish(struct simple_account_data *sip) {
 	gchar *uri = g_strdup_printf("sip:%s@%s", sip->username, sip->servername);
-	gchar *doc = gen_pidf(sip, FALSE);
-	send_sip_request(sip->gc, "PUBLISH", uri, uri,
-		"Expires: 600\r\nEvent: presence\r\n"
-		"Content-Type: application/pidf+xml\r\n",
+	gchar *add_headers, *doc;
+
+	add_headers = g_strdup_printf("%s%s%s%s",
+		sip->publish_etag ? "SIP-If-Match: " : "",
+		sip->publish_etag ? sip->publish_etag : "",
+		sip->publish_etag ? "\r\n" : "",
+		"Expires: 600\r\n"
+		"Event: presence\r\n"
+		"Content-Type: application/pidf+xml\r\n");
+
+	doc = gen_pidf(sip, FALSE);
+	send_sip_request(sip->gc, "PUBLISH", uri, uri, add_headers,
 		doc, NULL, process_publish_response);
 	/*sip->republish = time(NULL) + 500;*/
 	g_free(uri);
 	g_free(doc);
+	g_free(add_headers);
 }
 
 static void process_incoming_subscribe(struct simple_account_data *sip, struct sipmsg *msg) {
@@ -1653,7 +1675,7 @@
 		if(sip->fd == source) sip->fd = -1;
 		return;
 	}
-
+	gc->last_received = time(NULL);
 	conn->inbufused += len;
 	conn->inbuf[conn->inbufused] = '\0';
 
@@ -1948,6 +1970,7 @@
 		g_free(sip->proxy.target);
 		g_free(sip->proxy.realm);
 		g_free(sip->proxy.digest_session_key);
+		g_free(sip->publish_etag);
 		if(sip->txbuf)
 			purple_circ_buffer_destroy(sip->txbuf);
 		g_free(sip->realhostname);
--- a/libpurple/protocols/simple/simple.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/simple/simple.h	Wed Jan 30 10:53:22 2008 +0000
@@ -112,6 +112,7 @@
 	int registerexpire;
 	gchar *realhostname;
 	int realport; /* port and hostname from SRV record */
+	gchar *publish_etag;
 };
 
 struct sip_connection {
--- a/libpurple/protocols/yahoo/util.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/yahoo/util.c	Wed Jan 30 10:53:22 2008 +0000
@@ -32,6 +32,70 @@
 #include "util.h"
 
 #include <string.h>
+/*
+ * Returns cookies formatted as a null terminated string for the given connection.
+ * Must g_free return value.
+ * 
+ * TODO:will work, but must test for strict correctness
+ */
+gchar* yahoo_get_cookies(PurpleConnection *gc)
+{
+	gchar *ans = NULL;
+	gchar *cur;
+	char firstflag = 1;
+	gchar *t1,*t2,*t3;
+	GSList *tmp;
+	GSList *cookies;
+	cookies = ((struct yahoo_data*)(gc->proto_data))->cookies;
+	tmp = cookies;
+	while(tmp)
+	{
+		cur = tmp->data;
+		t1 = ans;
+		t2 = g_strrstr(cur, ";expires=");
+		if(t2 == NULL)
+			t2 = g_strrstr(cur, "; expires=");
+		if(t2 == NULL)
+		{
+			if(firstflag)
+				ans = g_strdup_printf("%c=%s", cur[0], cur+2);
+			else
+				ans = g_strdup_printf("%s; %c=%s", t1, cur[0], cur+2);
+		}
+		else
+		{
+			t3 = strstr(t2+1, ";");
+			if(t3 != NULL)
+			{
+				t2[0] = '\0';
+
+				if(firstflag)
+					ans = g_strdup_printf("%c=%s%s", cur[0], cur+2, t3);
+				else
+					ans = g_strdup_printf("%s; %c=%s%s", t1, cur[0], cur+2, t3);
+
+				t2[0] = ';';
+			}
+			else
+			{
+				t2[0] = '\0';
+
+				if(firstflag)
+					ans = g_strdup_printf("%c=%s", cur[0], cur+2);
+				else
+					ans = g_strdup_printf("%s; %c=%s", t1, cur[0], cur+2);
+
+				t2[0] = ';';
+			}
+		}
+		if(firstflag)
+			firstflag = 0;
+		else
+			g_free(t1);
+		tmp = g_slist_next(tmp);
+	}
+	return ans;
+}
 
 /**
  * Encode some text to send to the yahoo server.
@@ -121,6 +185,25 @@
 		return g_strdup("");
 }
 
+char *yahoo_convert_to_numeric(const char *str)
+{
+	GString *gstr = NULL;
+	char *retstr;
+	const unsigned char *p;
+
+	gstr = g_string_sized_new(strlen(str) * 6 + 1);
+
+	for (p = (unsigned char *)str; *p; p++) {
+		g_string_append_printf(gstr, "&#%u;", *p);
+	}
+
+	retstr = gstr->str;
+
+	g_string_free(gstr, FALSE);
+
+	return retstr;
+}
+
 /*
  * I found these on some website but i don't know that they actually
  * work (or are supposed to work). I didn't implement them yet.
--- a/libpurple/protocols/yahoo/yahoo.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Wed Jan 30 10:53:22 2008 +0000
@@ -392,6 +392,10 @@
 		case 97: /* Unicode status message */
 			unicode = !strcmp(pair->value, "1");
 			break;
+		case 244: /* client version number. Yahoo Client Detection */
+			if(f && strtol(pair->value, NULL, 10))
+				f->version_id = strtol(pair->value, NULL, 10);
+			break;
 
 		default:
 			purple_debug(PURPLE_DEBUG_ERROR, "yahoo",
@@ -493,16 +497,16 @@
 static void yahoo_process_cookie(struct yahoo_data *yd, char *c)
 {
 	if (c[0] == 'Y') {
-		g_free(yd->cookie_y);
+		if (yd->cookie_y)
+			g_free(yd->cookie_y);
 		yd->cookie_y = _getcookie(c);
 	} else if (c[0] == 'T') {
-		g_free(yd->cookie_t);
+		if (yd->cookie_t)
+			g_free(yd->cookie_t);
 		yd->cookie_t = _getcookie(c);
-	} else if (c[0] == 'C') {
-		g_free(yd->cookie_c);
-		yd->cookie_c = _getcookie(c);
 	} else
-		purple_debug_info("yahoo", "Ignoring unrecognized cookie '%c'\n", c[0]);
+		purple_debug_info("yahoo", "Unrecognized cookie '%c'\n", c[0]);
+	yd->cookies = g_slist_prepend(yd->cookies, g_strdup(c));
 }
 
 static void yahoo_process_list_15(PurpleConnection *gc, struct yahoo_packet *pkt)
@@ -510,6 +514,7 @@
 	GSList *l = pkt->hash;
 
 	PurpleAccount *account = purple_connection_get_account(gc);
+	struct yahoo_data *yd = gc->proto_data;
 	GHashTable *ht;
 	char *grp = NULL;
 	char *norm_bud = NULL;
@@ -575,6 +580,9 @@
 				purple_debug_info("yahoo", "Setting protocol to %d\n", f->protocol);
 			}
 			break;
+		case 59: /* somebody told cookies come here too, but im not sure */
+			yahoo_process_cookie(yd, pair->value);
+			break;
 		case 317: /* Stealth Setting */
 			if (f && (strtol(pair->value, NULL, 10) == 2)) {
 				f->presence = YAHOO_PRESENCE_PERM_OFFLINE;
@@ -1513,7 +1521,13 @@
 	to_y64(result96, digest, 16);
 
 	pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP,	YAHOO_STATUS_AVAILABLE, 0);
-	yahoo_packet_hash(pack, "ssss", 0, name, 6, result6, 96, result96, 1, name);
+	yahoo_packet_hash(pack, "ssssss",
+					  0, name,
+					  6, result6,
+					  96, result96,
+					  1, name,
+					  244, YAHOO_CLIENT_VERSION_ID,
+					  135, YAHOO_CLIENT_VERSION);
 	yahoo_packet_send_and_free(pack, yd);
 
 	g_free(hash_string_p);
@@ -1532,11 +1546,11 @@
 	char *enc_pass;
 	struct yahoo_data *yd = gc->proto_data;
 
-	PurpleCipher			*md5_cipher;
+	PurpleCipher		*md5_cipher;
 	PurpleCipherContext	*md5_ctx;
 	guchar				md5_digest[16];
 
-	PurpleCipher			*sha1_cipher;
+	PurpleCipher		*sha1_cipher;
 	PurpleCipherContext	*sha1_ctx1;
 	PurpleCipherContext	*sha1_ctx2;
 
@@ -1548,7 +1562,7 @@
 	char				*delimit_lookup		= ",;";
 
 	char				*password_hash		= (char *)g_malloc(25);
-	char				*crypt_hash		= (char *)g_malloc(25);
+	char				*crypt_hash			= (char *)g_malloc(25);
 	char				*crypt_result		= NULL;
 
 	unsigned char		pass_hash_xor1[64];
@@ -1602,7 +1616,7 @@
 
 	magic_ptr = seed;
 
-	while (*magic_ptr != (int)NULL) {
+	while (*magic_ptr != '\0') {
 		char   *loc;
 
 		/* Ignore parentheses. */
@@ -1655,7 +1669,7 @@
 	 * dust on the values.
 	 */
 
-	for (magic_cnt = magic_len-2; magic_cnt >= 0; magic_cnt--) {
+	for (magic_cnt = magic_len - 2; magic_cnt >= 0; magic_cnt--) {
 		unsigned char	byte1;
 		unsigned char	byte2;
 
@@ -1671,7 +1685,7 @@
 		byte1 ^= byte2;
 
 		magic[magic_cnt+1] = byte1;
-			}
+	}
 
 	/*
 	 * Magic: Phase 3.  This computes 20 bytes.  The first 4 bytes are used as our magic
@@ -1686,8 +1700,8 @@
 	x = 0;
 
 	do {
-		unsigned int	bl = 0;
-		unsigned int	cl = magic[magic_cnt++];
+		unsigned int bl = 0;
+		unsigned int cl = magic[magic_cnt++];
 
 		if (magic_cnt >= magic_len)
 			break;
@@ -1712,17 +1726,18 @@
 
 	/* First four bytes are magic key. */
 	memcpy(&magic_key_char[0], comparison_src, 4);
-	magic_4 = magic_key_char[0] | (magic_key_char[1]<<8) | (magic_key_char[2]<<16) | (magic_key_char[3]<<24);
+	magic_4 = magic_key_char[0] | (magic_key_char[1] << 8) |
+			(magic_key_char[2] << 16) | (magic_key_char[3] << 24);
 
 	/*
 	 * Magic: Phase 4.  Determine what function to use later by getting outside/inside
 	 * loop values until we match our previous buffer.
 	 */
 	for (x = 0; x < 65535; x++) {
-		int			leave = 0;
+		int leave = 0;
 
 		for (y = 0; y < 5; y++) {
-			unsigned char	test[3];
+			unsigned char test[3];
 
 			/* Calculate buffer. */
 			test[0] = x;
@@ -1958,8 +1973,13 @@
 	}
 	purple_debug_info("yahoo", "yahoo status: %d\n", yd->current_status);
 	pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP,	yd->current_status, 0);
-	yahoo_packet_hash(pack, "sssss", 0, name, 6, resp_6, 96, resp_96, 1,
-	                  name, 135, "6,0,0,1710");
+	yahoo_packet_hash(pack, "ssssss",
+					  0, name,
+					  6, resp_6,
+					  96, resp_96,
+					  1, name,
+					  244, YAHOO_CLIENT_VERSION_ID,
+					  135, YAHOO_CLIENT_VERSION);
 	if (yd->picture_checksum)
 		yahoo_packet_hash_int(pack, 192, yd->picture_checksum);
 
@@ -2449,12 +2469,16 @@
 	case YAHOO_SERVICE_AUDIBLE:
 		yahoo_process_audible(gc, pkt);
 		break;
-	case YAHOO_SERVICE_Y7_FILETRANSFER:
-		yahoo_process_y7_filetransfer(gc, pkt);
+	case YAHOO_SERVICE_FILETRANS_15:
+		yahoo_process_filetrans_15(gc, pkt);
 		break;
-	case YAHOO_SERVICE_Y7_FILETRANSFER_INFO:
-		yahoo_process_y7_filetransfer_info(gc, pkt);
+	case YAHOO_SERVICE_FILETRANS_INFO_15:
+		yahoo_process_filetrans_info_15(gc, pkt);
 		break;
+	case YAHOO_SERVICE_FILETRANS_ACC_15:
+		yahoo_process_filetrans_acc_15(gc, pkt);
+		break;
+
 	default:
 		purple_debug(PURPLE_DEBUG_ERROR, "yahoo",
 				   "Unhandled service 0x%02x\n", pkt->service);
@@ -2488,7 +2512,7 @@
 				_("Server closed the connection."));
 		return;
 	}
-
+	gc->last_received = time(NULL);
 	yd->rxqueue = g_realloc(yd->rxqueue, len + yd->rxlen);
 	memcpy(yd->rxqueue + yd->rxlen, buf, len);
 	yd->rxlen += len;
@@ -2676,11 +2700,15 @@
 	s = g_string_sized_new(len);
 
 	while ((i = strstr(i, "Set-Cookie: "))) {
+
 		i += strlen("Set-Cookie: ");
 		for (;*i != ';' && *i != '\0'; i++)
 			g_string_append_c(s, *i);
-
+        
 		g_string_append(s, "; ");
+		/* Should these cookies be included too when trying for xfer?
+		 * It seems to work without these
+		 */
 	}
 
 	yd->auth = g_string_free(s, FALSE);
@@ -2970,6 +2998,7 @@
 	yd->txbuf = purple_circ_buffer_new(0);
 	yd->friends = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, yahoo_friend_free);
 	yd->imvironments = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+	yd->xfer_peer_idstring_map = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
 	yd->confs = NULL;
 	yd->conf_id = 2;
 
@@ -3024,17 +3053,23 @@
 	}
 	g_slist_free(yd->confs);
 
+	for (l = yd->cookies; l; l = l->next) {
+		g_free(l->data);
+		l->data=NULL;
+	}
+	g_slist_free(yd->cookies);
+
 	yd->chat_online = 0;
 	if (yd->in_chat)
 		yahoo_c_leave(gc, 1); /* 1 = YAHOO_CHAT_ID */
 
 	g_hash_table_destroy(yd->friends);
 	g_hash_table_destroy(yd->imvironments);
+	g_hash_table_destroy(yd->xfer_peer_idstring_map);
 	g_free(yd->chat_name);
 
 	g_free(yd->cookie_y);
 	g_free(yd->cookie_t);
-	g_free(yd->cookie_c);
 
 	if (yd->txhandler)
 		purple_input_remove(yd->txhandler);
@@ -4144,17 +4179,13 @@
 
 GList *yahoo_attention_types(PurpleAccount *account)
 {
-	PurpleAttentionType *attn;
 	static GList *list = NULL;
 
 	if (!list) {
 		/* Yahoo only supports one attention command: the 'buzz'. */
 		/* This is index number YAHOO_BUZZ. */
-		attn = g_new0(PurpleAttentionType, 1);
-		attn->name = _("Buzz");
-		attn->incoming_description = _("%s has buzzed you!");
-		attn->outgoing_description = _("Buzzing %s...");
-		list = g_list_append(list, attn);
+		list = g_list_append(list, purple_attention_type_new("Buzz", _("Buzz"),
+				_("%s has buzzed you!"), _("Buzzing %s...")));
 	}
 
 	return list;
--- a/libpurple/protocols/yahoo/yahoo.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo.h	Wed Jan 30 10:53:22 2008 +0000
@@ -34,6 +34,8 @@
 #define YAHOO_MAIL_URL "https://login.yahoo.com/config/login?.src=ym"
 #define YAHOO_XFER_HOST "filetransfer.msg.yahoo.com"
 #define YAHOO_XFER_PORT 80
+#define YAHOO_XFER_RELAY_HOST "relay.msg.yahoo.com"
+#define YAHOO_XFER_RELAY_PORT 80
 #define YAHOO_ROOMLIST_URL "http://insider.msg.yahoo.com/ycontent/"
 #define YAHOO_ROOMLIST_LOCALE "us"
 /* really we should get the list of servers from
@@ -43,6 +45,11 @@
 #define YAHOOJP_MAIL_URL "http://mail.yahoo.co.jp/"
 #define YAHOOJP_XFER_HOST "filetransfer.msg.yahoo.co.jp"
 #define YAHOOJP_WEBCAM_HOST "wc.yahoo.co.jp"
+/*not sure, must test:*/
+#define YAHOOJP_XFER_RELAY_HOST "relay.msg.yahoo.com" 
+#define YAHOOJP_XFER_RELAY_PORT 80
+#define YAHOOJP_ROOMLIST_URL "http://insider.msg.yahoo.co.jp/ycontent/"
+#define YAHOOJP_ROOMLIST_LOCALE "ja"
 
 #define YAHOO_AUDIBLE_URL "http://us.dl1.yimg.com/download.yahoo.com/dl/aud"
 
@@ -67,6 +74,9 @@
 #define YAHOO_STATUS_TYPE_INVISIBLE "invisible"
 #define YAHOO_STATUS_TYPE_MOBILE "mobile"
 
+#define YAHOO_CLIENT_VERSION_ID "2097087"
+#define YAHOO_CLIENT_VERSION "8.1.0.421"
+
 /* Index into attention types list. */
 #define YAHOO_BUZZ 0
 
@@ -86,7 +96,8 @@
 	YAHOO_STATUS_IDLE = 999,
 	YAHOO_STATUS_WEBLOGIN = 0x5a55aa55,
 	YAHOO_STATUS_OFFLINE = 0x5a55aa56, /* don't ask */
-	YAHOO_STATUS_TYPING = 0x16
+	YAHOO_STATUS_TYPING = 0x16,
+	YAHOO_STATUS_DISCONNECTED = 0xffffffff /* in ymsg 15. doesnt mean the normal sense of 'disconnected' */
 };
 
 struct yahoo_buddy_icon_upload_data {
@@ -134,7 +145,6 @@
 	gsize auth_written;
 	char *cookie_y;
 	char *cookie_t;
-	char *cookie_c;
 	int session_id;
 	gboolean jp;
 	gboolean wm; /* connected w/ web messenger method */
@@ -154,6 +164,8 @@
 	 * for when we lookup people profile or photo information.
 	 */
 	GSList *url_datas;
+	GHashTable *xfer_peer_idstring_map;/*Hey, i dont know, but putting this HashTable next to friends gives a run time fault...*/
+	GSList *cookies;/*contains all cookies, including _y and _t*/
 };
 
 #define YAHOO_MAX_STATUS_MESSAGE_LENGTH (255)
@@ -206,12 +218,20 @@
  */
 char *yahoo_string_decode(PurpleConnection *gc, const char *str, gboolean utf8);
 
+char *yahoo_convert_to_numeric(const char *str);
+
 /* previously-static functions, now needed for yahoo_profile.c */
 void yahoo_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full);
 
 /* yahoo_profile.c */
 void yahoo_get_info(PurpleConnection *gc, const char *name);
 
+/* needed for xfer, thought theyd be useful for other enhancements later on
+   Returns list of cookies stored in yahoo_data formatted as a single null terminated string
+   returned value must be g_freed
+*/
+gchar* yahoo_get_cookies(PurpleConnection *gc);
+
 /**
  * Check to see whether the sender is permitted to send
  *
--- a/libpurple/protocols/yahoo/yahoo_aliases.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo_aliases.c	Wed Jan 30 10:53:22 2008 +0000
@@ -37,6 +37,8 @@
 /* I hate hardcoding this stuff, but Yahoo never sends us anything to use.  Someone in the know may be able to tweak this URL */
 #define YAHOO_ALIAS_FETCH_URL "http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us&diffs=1&t=0&tags=short&rt=0&prog-ver=8.1.0.249&useutf8=1&legenc=codepage-1252"
 #define YAHOO_ALIAS_UPDATE_URL "http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us&sync=1&tags=short&noclear=1&useutf8=1&legenc=codepage-1252"
+#define YAHOOJP_ALIAS_FETCH_URL "http://address.yahoo.co.jp/yab/jp?v=XM&prog=ymsgr&.intl=jp&diffs=1&t=0&tags=short&rt=0&prog-ver=7.0.0.7"
+#define YAHOOJP_ALIAS_UPDATE_URL "http://address.yahoo.co.jp/yab/jp?v=XM&prog=ymsgr&.intl=jp&sync=1&tags=short&noclear=1"
 
 void yahoo_update_alias(PurpleConnection *gc, const char *who, const char *alias);
 
@@ -90,7 +92,10 @@
 				id = xmlnode_get_attrib(item,"id");
 
 		                /* Yahoo stores first and last names separately, lets put them together into a full name */
-				full_name = g_strstrip(g_strdup_printf("%s %s", (fn != NULL ? fn : "") , (ln != NULL ? ln : "")));
+				if (yd->jp)
+					full_name = g_strstrip(g_strdup_printf("%s %s", (ln != NULL ? ln : "") , (fn != NULL ? fn : "")));
+				else
+					full_name = g_strstrip(g_strdup_printf("%s %s", (fn != NULL ? fn : "") , (ln != NULL ? ln : "")));
 				nick_name = (nn != NULL ? g_strstrip(g_strdup_printf("%s", nn)) : NULL);
 
 				if (nick_name != NULL)
@@ -139,7 +144,7 @@
 {
 	struct yahoo_data *yd = gc->proto_data;
 	struct callback_data *cb;
-	const char *url = YAHOO_ALIAS_FETCH_URL;
+	const char *url;
 	char *request, *webpage, *webaddress;
 	PurpleUtilFetchUrlData *url_data;
 
@@ -148,6 +153,7 @@
 	cb->gc = gc;
 
 	/*  Build all the info to make the web request */
+	url = yd->jp ? YAHOOJP_ALIAS_FETCH_URL : YAHOO_ALIAS_FETCH_URL;
 	purple_url_parse(url, &webaddress, NULL, &webpage, NULL, NULL);
 	request = g_strdup_printf("GET /%s HTTP/1.1\r\n"
 				 "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5)\r\n"
@@ -221,14 +227,15 @@
 	struct yahoo_data *yd;
 	struct YahooUser *yu;
 	char *content, *url, *request, *webpage, *webaddress, *strtmp;
+	char *escaped_alias, *alias_jp, *converted_alias_jp;
 	int inttmp;
 	struct callback_data *cb;
 	PurpleBuddy *buddy;
 	PurpleUtilFetchUrlData *url_data;
 
-	g_return_if_fail(alias!= NULL);
-	g_return_if_fail(who!=NULL);
-	g_return_if_fail(gc!=NULL);
+	g_return_if_fail(alias != NULL);
+	g_return_if_fail(who != NULL);
+	g_return_if_fail(gc != NULL);
 
 	purple_debug_info("yahoo", "Sending '%s' as new alias for user '%s'.\n",alias, who);
 
@@ -247,12 +254,25 @@
 	cb->gc = gc;
 
 	/*  Build all the info to make the web request */
-	url = g_strdup(YAHOO_ALIAS_UPDATE_URL);
+	url = yd->jp? YAHOOJP_ALIAS_UPDATE_URL: YAHOO_ALIAS_UPDATE_URL;
 	purple_url_parse(url, &webaddress, &inttmp, &webpage, &strtmp, &strtmp);
 
-	content = g_strdup_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?><ab k=\"%s\" cc=\"1\">\n"
-				  "<ct e=\"1\"  yi='%s' id='%s' nn='%s' pr='0' />\n</ab>\r\n",
-				  gc->account->username, who, yu->id, g_markup_escape_text(alias, strlen(alias)));
+	if (yd->jp) {
+		alias_jp = g_convert(alias, strlen(alias), "EUC-JP", "UTF-8", NULL, NULL, NULL);
+		converted_alias_jp = yahoo_convert_to_numeric(alias_jp);
+		content = g_strdup_printf("<ab k=\"%s\" cc=\"1\">\n"
+		                          "<ct e=\"1\"  yi='%s' id='%s' nn='%s' pr='0' />\n</ab>\r\n",
+		                          gc->account->username, who, yu->id, converted_alias_jp);
+		free(converted_alias_jp);
+		g_free(alias_jp);
+	}
+	else {
+		escaped_alias = g_markup_escape_text(alias, strlen(alias));
+		content = g_strdup_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?><ab k=\"%s\" cc=\"1\">\n"
+		                          "<ct e=\"1\"  yi='%s' id='%s' nn='%s' pr='0' />\n</ab>\r\n",
+		                          gc->account->username, who, yu->id, escaped_alias);
+		g_free(escaped_alias);
+	}
 
 	request = g_strdup_printf("POST /%s HTTP/1.1\r\n"
 				  "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5)\r\n"
@@ -271,7 +291,6 @@
 	}
 
 	g_free(content);
-	g_free(url);
 	g_free(request);
 }
 
--- a/libpurple/protocols/yahoo/yahoo_filexfer.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.c	Wed Jan 30 10:53:22 2008 +0000
@@ -21,6 +21,7 @@
  */
 
 #include "internal.h"
+#include "dnsquery.h"
 
 #include "prpl.h"
 #include "util.h"
@@ -32,6 +33,7 @@
 #include "yahoo_packet.h"
 #include "yahoo_filexfer.h"
 #include "yahoo_doodle.h"
+#include "yahoo_friend.h"
 
 struct yahoo_xfer_data {
 	gchar *host;
@@ -46,81 +48,65 @@
 	guint tx_handler;
 	gchar *rxqueue;
 	guint rxlen;
+	gchar *xfer_peer_idstring;
+	gchar *xfer_idstring_for_relay;
+	int version; /*0 for old, 15 for Y7(YMSG 15)*/
+	int info_val_249;
 
-	gboolean y7;	/* true for Y7 transfers (receive only for now) */
-	gchar *token;
-	gchar *tid;
+	enum {
+		STARTED = 0,
+		HEAD_REQUESTED,
+		HEAD_REPLY_RECEIVED,
+		TRANSFER_PHASE,
+		ACCEPTED
+	} status_15;
+
+	/* contains all filenames, in case of multiple transfers, with the first
+	 * one in the list being the current file's name (ymsg15) */
+	GSList *filename_list;
+	GSList *size_list; /*corresponds to filename_list, with size as **STRING** */
+	gboolean firstoflist;
 };
 
 static void yahoo_xfer_data_free(struct yahoo_xfer_data *xd)
 {
+	PurpleConnection *gc;
+	struct yahoo_data *yd;
+	PurpleXfer *xfer;
+	GSList *l;
+
+	gc = xd->gc;
+	yd = gc->proto_data;
+
+	/*remove entry from map*/
+	if(xd->xfer_peer_idstring) {
+		xfer = g_hash_table_lookup(yd->xfer_peer_idstring_map, xd->xfer_peer_idstring);
+		if(xfer)
+			g_hash_table_remove(yd->xfer_peer_idstring_map, xd->xfer_peer_idstring);
+	}
+
+	/*empty file & filesize list*/
+	for (l = xd->filename_list; l; l = l->next) {
+		g_free(l->data);
+		l->data=NULL;
+	}
+	for (l = xd->size_list; l; l = l->next) {
+		g_free(l->data);
+		l->data=NULL;
+	}
+	g_slist_free(xd->filename_list);
+	g_slist_free(xd->size_list);
+
 	g_free(xd->host);
 	g_free(xd->path);
 	g_free(xd->txbuf);
-	g_free(xd->token);
-	g_free(xd->tid);
+	g_free(xd->xfer_peer_idstring);
+	g_free(xd->xfer_idstring_for_relay);
 	if (xd->tx_handler)
 		purple_input_remove(xd->tx_handler);
 	g_free(xd);
 }
 
-
-static void yahoo_xfer_y7_request_next_file(PurpleXfer *xfer)
-{
-	struct yahoo_packet *pack;
-	struct yahoo_xfer_data *xd = xfer->data;
-	PurpleConnection *gc = xd->gc;
-	struct yahoo_data *yd = gc->proto_data;
-
-	g_return_if_fail(xd->y7);
-
-	pack = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER_ACCEPT, YAHOO_STATUS_AVAILABLE, 0);
-	yahoo_packet_hash(pack, "sssi",
-		1, purple_connection_get_display_name(xd->gc),
-		5, xfer->who,
-		265, xd->tid,
-		271, 1);
-	yahoo_packet_send_and_free(pack, yd);
-}
-
-static void yahoo_xfer_y7_cancel_receive(PurpleXfer *xfer)
-{
-	struct yahoo_packet *pack;
-	struct yahoo_xfer_data *xd = xfer->data;
-	PurpleConnection *gc = xd->gc;
-	struct yahoo_data *yd = gc->proto_data;
-
-	g_return_if_fail(xd->y7);
-
-	pack = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER_ACCEPT, -1, 0);
-	yahoo_packet_hash(pack, "sssi",
-		1, purple_connection_get_display_name(gc),
-		5, xfer->who,
-		265, xd->tid,
-		66, -1);
-	yahoo_packet_send_and_free(pack, yd);
-}
-
-static void yahoo_xfer_y7_accept_file(PurpleXfer *xfer)
-{
-	struct yahoo_packet *pack;
-	struct yahoo_xfer_data *xd = xfer->data;
-	PurpleConnection *gc = xd->gc;
-	struct yahoo_data *yd = gc->proto_data;
-
-	g_return_if_fail(xd->y7);
-
-	pack = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER_ACCEPT, YAHOO_STATUS_AVAILABLE, 0);
-	yahoo_packet_hash(pack, "ssssis",
-		1, purple_connection_get_display_name(gc),
-		5, xfer->who,				/* XXX this needs an accessor */
-		265, xd->tid,
-		27, purple_xfer_get_filename(xfer),	/* XXX this might be of incorrect encoding */
-		249, 3,
-		251, xd->token);
-	yahoo_packet_send_and_free(pack, yd);
-}
-
 static void yahoo_receivefile_send_cb(gpointer data, gint source, PurpleInputCondition condition)
 {
 	PurpleXfer *xfer;
@@ -160,7 +146,6 @@
 {
 	PurpleXfer *xfer;
 	struct yahoo_xfer_data *xd;
-	struct yahoo_data *yd;
 
 	purple_debug(PURPLE_DEBUG_INFO, "yahoo",
 			   "AAA - in yahoo_receivefile_connected\n");
@@ -176,22 +161,11 @@
 	}
 
 	xfer->fd = source;
-	yd = xd->gc->proto_data;
 
 	/* The first time we get here, assemble the tx buffer */
 	if (xd->txbuflen == 0) {
-		if (!xd->y7)
-			xd->txbuf = g_strdup_printf("GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n",
-				      xd->path, xd->host);
-		else
-			xd->txbuf = g_strdup_printf("GET /%s HTTP/1.0\r\n"
-				"Connection: close\r\n"
-				"Accept: */*\r\n"
-				"Host: %s\r\n"
-				"Cookie: Y=%s; T=%s\r\n"
-				"\r\n",
-				xd->path, xd->host, yd->cookie_y, yd->cookie_t);
-		purple_debug(PURPLE_DEBUG_INFO, "yahoo_filexfer", "HTTP request: [%s]\n", xd->txbuf);
+		xd->txbuf = g_strdup_printf("GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n",
+			      xd->path, xd->host);
 		xd->txbuflen = strlen(xd->txbuf);
 		xd->txbuf_written = 0;
 	}
@@ -361,9 +335,6 @@
 			}
 		}
 	} else {
-		if (xfer_data->y7)
-			yahoo_xfer_y7_accept_file(xfer);
-
 		xfer->fd = -1;
 		if (purple_proxy_connect(NULL, account, xfer_data->host, xfer_data->port,
 		                              yahoo_receivefile_connected, xfer) == NULL) {
@@ -374,23 +345,67 @@
 	}
 }
 
+static void yahoo_xfer_init_15(PurpleXfer *xfer)
+{
+	struct yahoo_xfer_data *xfer_data;
+	PurpleConnection *gc;
+	PurpleAccount *account;
+	struct yahoo_data *yd;
+	struct yahoo_packet *pkt;
+
+	xfer_data = xfer->data;
+	gc = xfer_data->gc;
+	yd = gc->proto_data;
+	account = purple_connection_get_account(gc);
+
+	if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND)	{
+		gchar *filename;
+		filename = g_path_get_basename(purple_xfer_get_local_filename(xfer));
+		pkt = yahoo_packet_new(YAHOO_SERVICE_FILETRANS_15,
+							   YAHOO_STATUS_AVAILABLE,
+							   yd->session_id);
+		yahoo_packet_hash(pkt, "sssiiiisiii",
+			1, purple_normalize(account, purple_account_get_username(account)),
+			5, xfer->who,
+			265, xfer_data->xfer_peer_idstring,
+			222, 1,
+			266, 1,
+			302, 268,
+			300, 268,
+			27,  filename,
+			28,  xfer->size,
+			301, 268,
+			303, 268);
+		g_free(filename); 
+	} else {
+		if(xfer_data->firstoflist == TRUE) {
+			pkt = yahoo_packet_new(YAHOO_SERVICE_FILETRANS_15,
+				YAHOO_STATUS_AVAILABLE, yd->session_id);
+	
+			yahoo_packet_hash(pkt, "sssi",
+				1, purple_normalize(account, purple_account_get_username(account)),
+				5, xfer->who,
+				265, xfer_data->xfer_peer_idstring,
+				222, 3);
+		} else {
+			pkt = yahoo_packet_new(YAHOO_SERVICE_FILETRANS_ACC_15,
+				YAHOO_STATUS_AVAILABLE, yd->session_id);
+	
+			yahoo_packet_hash(pkt, "sssi",
+				1, purple_normalize(account, purple_account_get_username(account)),
+				5, xfer->who,
+				265, xfer_data->xfer_peer_idstring,
+				271, 1);
+		}
+	}
+	yahoo_packet_send_and_free(pkt, yd);
+}
+
 static void yahoo_xfer_start(PurpleXfer *xfer)
 {
 	/* We don't need to do anything here, do we? */
 }
 
-static void yahoo_xfer_end(PurpleXfer *xfer)
-{
-	struct yahoo_xfer_data *xfer_data;
-
-	xfer_data = xfer->data;
-
-	if (xfer_data)
-		yahoo_xfer_data_free(xfer_data);
-	xfer->data = NULL;
-
-}
-
 static guint calculate_length(const gchar *l, size_t len)
 {
 	int i;
@@ -423,8 +438,6 @@
 		if ((purple_xfer_get_size(xfer) > 0) &&
 		    (purple_xfer_get_bytes_sent(xfer) >= purple_xfer_get_size(xfer))) {
 			purple_xfer_set_completed(xfer, TRUE);
-			if (xd->y7)
-				yahoo_xfer_y7_request_next_file(xfer);
 			return 0;
 		} else
 			return -1;
@@ -504,6 +517,42 @@
 
 	xfer_data = xfer->data;
 
+	if(purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL && xfer_data->version == 15)
+	{
+		PurpleConnection *gc;
+		PurpleAccount *account;
+		struct yahoo_data *yd;
+		struct yahoo_packet *pkt;
+
+		gc = xfer_data->gc;
+		yd = gc->proto_data;
+		account = purple_connection_get_account(gc);
+		if(xfer_data->xfer_idstring_for_relay) /* hack to see if file trans acc/info packet has been received */
+		{
+			pkt = yahoo_packet_new(YAHOO_SERVICE_FILETRANS_INFO_15,
+								   YAHOO_STATUS_DISCONNECTED,
+								   yd->session_id);
+			yahoo_packet_hash(pkt, "sssi",
+				1, purple_normalize(account, purple_account_get_username(account)),
+				5, xfer->who,
+				265, xfer_data->xfer_peer_idstring,
+				66, -1);
+		}
+		else
+		{
+			pkt = yahoo_packet_new(YAHOO_SERVICE_FILETRANS_15,
+								   YAHOO_STATUS_AVAILABLE,
+								   yd->session_id);
+			yahoo_packet_hash(pkt, "sssi",
+				1, purple_normalize(account, purple_account_get_username(account)),
+				5, xfer->who,
+				265, xfer_data->xfer_peer_idstring,
+				222, 2);
+		}
+		yahoo_packet_send_and_free(pkt, yd);
+	}
+
+
 	if (xfer_data)
 		yahoo_xfer_data_free(xfer_data);
 	xfer->data = NULL;
@@ -515,22 +564,145 @@
 
 	xfer_data = xfer->data;
 
-	if (xfer_data) {
-		if (xfer_data->y7)
-			yahoo_xfer_y7_cancel_receive(xfer);
+	if(purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL && xfer_data->version == 15)
+	{
+
+		PurpleConnection *gc;
+		PurpleAccount *account;
+		struct yahoo_data *yd;
+		struct yahoo_packet *pkt;
+
+		gc = xfer_data->gc;
+		yd = gc->proto_data;
+		account = purple_connection_get_account(gc);
+		if(!xfer_data->xfer_idstring_for_relay) /* hack to see if file trans acc/info packet has been received */
+		{
+			pkt = yahoo_packet_new(YAHOO_SERVICE_FILETRANS_15,
+								   YAHOO_STATUS_AVAILABLE,
+								   yd->session_id);
+			yahoo_packet_hash(pkt, "sssi",
+				1, purple_normalize(account, purple_account_get_username(account)),
+				5, xfer->who,
+				265, xfer_data->xfer_peer_idstring,
+				222, 4);
+		}
+		else
+		{
+			pkt = yahoo_packet_new(YAHOO_SERVICE_FILETRANS_15,
+								   YAHOO_STATUS_DISCONNECTED,
+								   yd->session_id);
+			yahoo_packet_hash(pkt, "sssi",
+				1, purple_normalize(account, purple_account_get_username(account)),
+				5, xfer->who,
+				265, xfer_data->xfer_peer_idstring,
+				66, -1);
+		}
+		yahoo_packet_send_and_free(pkt, yd);
+	}
+
+	if (xfer_data)
 		yahoo_xfer_data_free(xfer_data);
-	}
 	xfer->data = NULL;
 }
 
-static void yahoo_xfer_request_denied(PurpleXfer *xfer)
+static void yahoo_xfer_end(PurpleXfer *xfer_old)
 {
 	struct yahoo_xfer_data *xfer_data;
+	PurpleXfer *xfer = NULL;
+	PurpleConnection *gc;
+	struct yahoo_data *yd;
 
-	xfer_data = xfer->data;
+	xfer_data = xfer_old->data;
+	if(xfer_data && xfer_data->version == 15
+	   && purple_xfer_get_type(xfer_old) == PURPLE_XFER_RECEIVE
+	   && xfer_data->filename_list) {
+		
+		/* removing top of filename & size list completely */
+		g_free( xfer_data->filename_list->data );
+		g_free( xfer_data->size_list->data );
+		   
+		xfer_data->filename_list->data = NULL;
+		xfer_data->size_list->data = NULL;
+		   
+		xfer_data->filename_list = g_slist_delete_link(xfer_data->filename_list, xfer_data->filename_list);
+		xfer_data->size_list = g_slist_delete_link(xfer_data->size_list, xfer_data->size_list);
+
+		/* if there are still more files */
+		if(xfer_data->filename_list)
+		{
+			gchar* filename;
+			long filesize;
+
+			filename = xfer_data->filename_list->data;
+			filesize = atol( xfer_data->size_list->data );
+
+			gc = xfer_data->gc;
+			yd = gc->proto_data;
 
-	if (xfer_data->y7)
-		yahoo_xfer_y7_cancel_receive(xfer);
+			/* setting up xfer_data for next file's tranfer */
+			g_free(xfer_data->host);
+			g_free(xfer_data->path);
+			g_free(xfer_data->txbuf);
+			g_free(xfer_data->rxqueue);
+			g_free(xfer_data->xfer_idstring_for_relay);
+			if (xfer_data->tx_handler)
+				purple_input_remove(xfer_data->tx_handler);
+			xfer_data->host = NULL;
+			xfer_data->host = NULL;
+			xfer_data->port = 0;
+			xfer_data->expires = 0;
+			xfer_data->started = FALSE;
+			xfer_data->txbuf = NULL;
+			xfer_data->txbuflen = 0;
+			xfer_data->txbuf_written = 0;
+			xfer_data->tx_handler = 0;
+			xfer_data->rxqueue = NULL;
+			xfer_data->rxlen = 0;
+			xfer_data->xfer_idstring_for_relay = NULL;
+			xfer_data->info_val_249 = 0;
+			xfer_data->status_15 = STARTED;
+			xfer_data->firstoflist = FALSE;
+
+			/* Dereference xfer_data from old xfer */
+			xfer_old->data = NULL;
+
+			/* Build the file transfer handle. */
+			xfer = purple_xfer_new(gc->account, PURPLE_XFER_RECEIVE, xfer_old->who);
+
+			
+			if (xfer) {
+				/* Set the info about the incoming file. */
+				char *utf8_filename = yahoo_string_decode(gc, filename, TRUE);
+				purple_xfer_set_filename(xfer, utf8_filename);
+				g_free(utf8_filename);
+				purple_xfer_set_size(xfer, filesize);
+		
+				xfer->data = xfer_data;
+	
+				/* Setup our I/O op functions */
+				purple_xfer_set_init_fnc(xfer,        yahoo_xfer_init_15);
+				purple_xfer_set_start_fnc(xfer,       yahoo_xfer_start);
+				purple_xfer_set_end_fnc(xfer,         yahoo_xfer_end);
+				purple_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send);
+				purple_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv);
+				purple_xfer_set_read_fnc(xfer,        yahoo_xfer_read);
+				purple_xfer_set_write_fnc(xfer,       yahoo_xfer_write);
+				purple_xfer_set_request_denied_fnc(xfer,yahoo_xfer_cancel_recv);
+
+				/*update map to current xfer*/
+				g_hash_table_remove(yd->xfer_peer_idstring_map, xfer_data->xfer_peer_idstring);
+				g_hash_table_insert(yd->xfer_peer_idstring_map, xfer_data->xfer_peer_idstring, xfer);
+
+				/* Now perform the request */
+				purple_xfer_request(xfer);
+			}
+			return;
+		}
+	}
+	if (xfer_data)
+		yahoo_xfer_data_free(xfer_data);
+	xfer_old->data = NULL;
+
 }
 
 void yahoo_process_p2pfilexfer(PurpleConnection *gc, struct yahoo_packet *pkt)
@@ -726,165 +898,6 @@
 	}
 }
 
-void yahoo_process_y7_filetransfer(PurpleConnection *gc, struct yahoo_packet *pkt)
-{
-	struct yahoo_data *yd = gc->proto_data;
-	char *who = NULL, *name = NULL;
-	int ttype = 0;
-	char *tid = NULL;
-	GSList *l = pkt->hash;
-
-	while (l) {
-		struct yahoo_pair *pair = l->data;
-
-		switch (pair->key) {
-		case 4:
-			/* them */
-			who = pair->value;
-			break;
-		case 5:
-			/* us */
-			name = pair->value;
-			break;
-		case 222:
-			/* 1=send, 2=cancel, 3=accept, 4=reject */
-			if(pair->value)
-				ttype = atoi(pair->value);
-			break;
-		case 265:
-			/* transfer ID */
-			tid = pair->value;
-			break;
-		case 266:
-			/* number of files */
-			break;
-		case 27:
-			/* filename */
-			break;
-		case 28:
-			/* filesize */
-			break;
-		}
-
-		l = l->next;
-	}
-	if (ttype == 1 && tid) {
-		/* We auto-accept all offers here, and ask the user about each individual
-		 * file in yahoo_process_y7_filetransfer_info. This works fine for receiving
-		 * a single file; when receiving multiple canceling one in the middle
-		 * will also cancel the rest of them.
-		 * Maybe TODO: UI and API allowing transfer of multiple files as a package. */
-		struct yahoo_packet *pack;
-		pack = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER, YAHOO_STATUS_AVAILABLE, 0);
-		yahoo_packet_hash(pack, "sssi", 1, name, 5, who, 265, tid, 222, 3);
-		yahoo_packet_send_and_free(pack, yd);
-	}
-}
-
-void yahoo_process_y7_filetransfer_info(PurpleConnection *gc, struct yahoo_packet *pkt)
-{
-	struct yahoo_data *yd = gc->proto_data;
-	char *who = NULL, *name = NULL;
-	int medium = 0;
-	char *tid = NULL, *server_host = NULL, *server_token = NULL, *filename = NULL;
-	GSList *l = pkt->hash;
-	struct yahoo_packet *pack;
-	PurpleXfer *xfer;
-	struct yahoo_xfer_data *xfer_data;
-
-	while (l) {
-		struct yahoo_pair *pair = l->data;
-
-		switch (pair->key) {
-		case 4:
-			/* them */
-			who = pair->value;
-			break;
-		case 5:
-			/* us */
-			name = pair->value;
-			break;
-		case 249:
-			/* 1=p2p, 3=reflection server */
-			if(pair->value)
-				medium = atoi(pair->value);
-			break;
-		case 265:
-			/* transfer ID */
-			tid = pair->value;
-			break;
-		case 27:
-			filename = pair->value;
-			break;
-		case 250:
-			server_host = pair->value;
-			break;
-		case 251:
-			server_token = pair->value;
-			break;
-		}
-
-		l = l->next;
-	}
-	if (medium == 1) {
-		/* reject P2P transfers */
-		pack = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER_ACCEPT, YAHOO_STATUS_AVAILABLE, 0);
-		yahoo_packet_hash(pack, "sssi", 1, name, 5, who, 265, tid, 66, -3);
-		yahoo_packet_send_and_free(pack, yd);
-		return;
-	}
-
-	if (medium != 3) {
-		purple_debug_error("yahoo", "Unexpected medium %d.\n", medium);
-		/* weird */
-		return;
-	}
-
-	/* Setup the Yahoo-specific file transfer data */
-	xfer_data = g_new0(struct yahoo_xfer_data, 1);
-	xfer_data->gc = gc;
-	xfer_data->host = g_strdup(server_host);
-	xfer_data->token = g_strdup(server_token);
-	xfer_data->tid = g_strdup(tid);
-	xfer_data->port = 80;
-	xfer_data->y7 = TRUE;
-	
-	/* TODO: full urlencode here */
-	server_token = purple_strreplace(server_token, "\002", "%02");
-	xfer_data->path = g_strdup_printf("relay?token=%s&sender=%s&recver=%s",
-		server_token, who, name);
-	g_free(server_token);
-
-	purple_debug_misc("yahoo_filexfer", "Host is %s, port is %d, path is %s.\n",
-	                xfer_data->host, xfer_data->port, xfer_data->path);
-
-	/* Build the file transfer handle. */
-	xfer = purple_xfer_new(gc->account, PURPLE_XFER_RECEIVE, who);
-	xfer->data = xfer_data;
-
-	/* Set the info about the incoming file. */
-	{
-		char *utf8_filename = yahoo_string_decode(gc, filename, TRUE);
-		purple_xfer_set_filename(xfer, utf8_filename);
-		g_free(utf8_filename);
-	}
-
-	/* purple_xfer_set_size(xfer, filesize); */
-
-	/* Setup our I/O op functions */
-	purple_xfer_set_init_fnc(xfer,        yahoo_xfer_init);
-	purple_xfer_set_start_fnc(xfer,       yahoo_xfer_start);
-	purple_xfer_set_end_fnc(xfer,         yahoo_xfer_end);
-	purple_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send);
-	purple_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv);
-	purple_xfer_set_read_fnc(xfer,        yahoo_xfer_read);
-	purple_xfer_set_write_fnc(xfer,       yahoo_xfer_write);
-	purple_xfer_set_request_denied_fnc(xfer, yahoo_xfer_request_denied);
-
-	/* Now perform the request */
-	purple_xfer_request(xfer);
-}
-
 PurpleXfer *yahoo_new_xfer(PurpleConnection *gc, const char *who)
 {
 	PurpleXfer *xfer;
@@ -914,15 +927,641 @@
 	return xfer;
 }
 
+static gchar* yahoo_xfer_new_xfer_id(void)
+{
+	gchar *ans;
+	int i,j;
+	ans = g_strnfill(24, ' ');
+	ans[23] = '$';
+	ans[22] = '$';
+	for(i = 0; i < 22; i++)
+	{
+		j = g_random_int_range (0,61);
+		if(j < 26)
+			ans[i] = j + 'a';
+		else if(j < 52)
+			ans[i] = j - 26 + 'A';
+		else
+			ans[i] = j - 52 + '0';
+	}
+	return ans;
+}
+
+static void yahoo_xfer_dns_connected_15(GSList *hosts, gpointer data, const char *error_message)
+{
+	PurpleXfer *xfer;
+	struct yahoo_xfer_data *xd;
+	struct sockaddr_in *addr;
+	struct yahoo_packet *pkt;
+	long actaddr;
+	long a,b,c,d;
+	PurpleConnection *gc;
+	PurpleAccount *account;
+	struct yahoo_data *yd;
+	gchar *url;
+	gchar *filename;
+
+	if (!(xfer = data))
+		return;
+	if (!(xd = xfer->data))
+		return;
+	gc = xd->gc;
+	account = purple_connection_get_account(gc);
+	yd = gc->proto_data;
+
+	if(!hosts)
+	{
+		purple_debug_error("yahoo", "Unable to find an IP address for relay.msg.yahoo.com\n");
+		purple_xfer_cancel_remote(xfer);
+		return;
+	}
+	
+	/* Discard the length... */
+	hosts = g_slist_remove(hosts, hosts->data);
+	if(!hosts)
+	{
+		purple_debug_error("yahoo", "Unable to find an IP address for relay.msg.yahoo.com\n");
+		purple_xfer_cancel_remote(xfer);
+		return;
+	}
+	
+	/*TODO:actually, u must try with addr no.1 , if its not working addr no.2 .....*/
+	addr = hosts->data;
+	actaddr = addr->sin_addr.s_addr;
+	d = actaddr % 256;
+	actaddr = (actaddr - d) / 256;
+	c = actaddr % 256;
+	actaddr = (actaddr - c) / 256;
+	b = actaddr % 256;
+	actaddr = (actaddr - b) / 256;
+	a = actaddr;
+	if(yd->jp)
+		xd->port = YAHOOJP_XFER_RELAY_PORT;
+	else
+		xd->port = YAHOO_XFER_RELAY_PORT;
+
+	url = g_strdup_printf("%ld.%ld.%ld.%ld", d, c, b, a);
+	if (!purple_url_parse(url, &(xd->host), &(xd->port), &(xd->path), NULL, NULL)) {
+		purple_xfer_cancel_remote(xfer);
+		return;
+	}
+	g_free(url);
+	/* Free the address... */
+	g_free(hosts->data);
+	hosts = g_slist_remove(hosts, hosts->data);
+	addr = NULL;
+	while (hosts != NULL)
+	{
+		/* Discard the length... */
+		hosts = g_slist_remove(hosts, hosts->data);
+		/* Free the address... */
+		g_free(hosts->data);
+		hosts = g_slist_remove(hosts, hosts->data);
+	}
+
+	pkt = yahoo_packet_new(YAHOO_SERVICE_FILETRANS_INFO_15, YAHOO_STATUS_AVAILABLE, yd->session_id);
+	filename = g_path_get_basename(purple_xfer_get_local_filename(xfer));
+
+	yahoo_packet_hash(pkt, "ssssis",
+		1, purple_normalize(account, purple_account_get_username(account)),
+		5, xfer->who,
+		265, xd->xfer_peer_idstring,
+		27,  filename,
+		249, 3,
+		250, xd->host);
+
+	g_free(filename);
+	yahoo_packet_send_and_free(pkt, yd);
+}
+
+
 void yahoo_send_file(PurpleConnection *gc, const char *who, const char *file)
 {
+	struct yahoo_xfer_data *xfer_data;
+	struct yahoo_data *yd;
+	int ver = 0;
 	PurpleXfer *xfer = yahoo_new_xfer(gc, who);
+	YahooFriend *yf = yahoo_friend_find(gc, who);
 
+	/* To determine whether client uses ymsg 15 i.e. client is higher than YM 7 */
+	if(yf && yf->version_id > 500000)
+		ver=15; 
 	g_return_if_fail(xfer != NULL);
 
+	if(ver == 15) {
+		yd = gc->proto_data;
+		xfer_data = xfer->data;
+		xfer_data->status_15 = STARTED;
+		purple_xfer_set_init_fnc(xfer, yahoo_xfer_init_15);
+		xfer_data->version = 15;
+		xfer_data->xfer_peer_idstring = yahoo_xfer_new_xfer_id();
+		g_hash_table_insert(yd->xfer_peer_idstring_map, xfer_data->xfer_peer_idstring, xfer);
+	}
+
 	/* Now perform the request */
 	if (file)
 		purple_xfer_request_accepted(xfer, file);
 	else
 		purple_xfer_request(xfer);
 }
+
+static void yahoo_xfer_connected_15(gpointer data, gint source, const gchar *error_message);/*using this in recv_cb*/
+static void yahoo_xfer_recv_cb_15(gpointer data, gint source, PurpleInputCondition condition)
+{
+	PurpleXfer *xfer;
+	struct yahoo_xfer_data *xd;
+	int did;
+	gchar* buf;
+	gchar* t;
+	PurpleAccount *account;
+	PurpleConnection *gc;
+
+	xfer = data;
+	xd = xfer->data;
+	account = purple_connection_get_account(xd->gc);
+	gc = xd->gc;
+
+	buf=g_strnfill(1000, 0);
+	while((did = read(source, buf, 998)) > 0)
+	{
+		xd->txbuflen += did;
+		buf[did] = '\0';  
+		t = xd->txbuf;
+		xd->txbuf = g_strconcat(t,buf,NULL);
+		g_free(t);
+	}
+	g_free(buf);
+
+	if (did < 0 && errno == EAGAIN) return;
+	else if (did < 0) {
+		purple_debug_error("yahoo", "Unable to write in order to start ft errno = %d\n", errno);
+		purple_xfer_cancel_remote(xfer);
+		return;
+	}
+
+	purple_input_remove(xd->tx_handler);
+	xd->tx_handler = 0;
+	xd->txbuflen = 0;
+
+	if(xd->status_15 == HEAD_REQUESTED) {
+		xd->status_15 = HEAD_REPLY_RECEIVED;
+		close(source);/*Is this required?*/
+		g_free(xd->txbuf);
+		xd->txbuf = NULL;
+		if (purple_proxy_connect(NULL, account, xd->host, xd->port, yahoo_xfer_connected_15, xfer) == NULL)
+		{
+			purple_notify_error(gc, NULL, _("File Transfer Failed"),
+				_("Unable to establish file descriptor."));
+			purple_xfer_cancel_remote(xfer);
+		}
+	} else {
+		purple_debug_error("yahoo","Unrecognized yahoo file transfer mode and stage (ymsg15):%d,%d\n",
+						   purple_xfer_get_type(xfer),
+						   xd->status_15);
+		return;
+	}
+}
+
+static void yahoo_xfer_send_cb_15(gpointer data, gint source, PurpleInputCondition condition)
+{
+	PurpleXfer *xfer;
+	struct yahoo_xfer_data *xd;
+	int remaining, written;
+
+	xfer = data;
+	xd = xfer->data;
+	remaining = xd->txbuflen - xd->txbuf_written;
+	written = write(source, xd->txbuf + xd->txbuf_written, remaining);
+
+	if (written < 0 && errno == EAGAIN)
+		written = 0;
+	else if (written <= 0) {
+		purple_debug_error("yahoo", "Unable to write in order to start ft errno = %d\n", errno);
+		purple_xfer_cancel_remote(xfer);
+		return;
+	}
+
+	if (written < remaining) {
+		xd->txbuf_written += written;
+		return;
+	}
+
+	purple_input_remove(xd->tx_handler);
+	xd->tx_handler = 0;
+	g_free(xd->txbuf);
+	xd->txbuf = NULL;
+	xd->txbuflen = 0;
+	xd->txbuf_written = 0;
+
+	if(purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE && xd->status_15 == STARTED)
+	{	
+		xd->status_15 = HEAD_REQUESTED;
+		xd->tx_handler = purple_input_add(source, PURPLE_INPUT_READ, yahoo_xfer_recv_cb_15, xfer);
+		yahoo_xfer_recv_cb_15(xfer, source, PURPLE_INPUT_READ);
+	}
+	else if(purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE && xd->status_15 == HEAD_REPLY_RECEIVED)
+	{
+		xd->status_15 = TRANSFER_PHASE;
+		xfer->fd = source;
+		purple_xfer_start(xfer, source, NULL, 0);
+	}
+	else if(purple_xfer_get_type(xfer) == PURPLE_XFER_SEND && xd->status_15 == ACCEPTED)
+	{
+		xd->status_15 = TRANSFER_PHASE;
+		xfer->fd = source;
+		purple_xfer_start(xfer, source, NULL, 0);
+	}
+	else
+	{
+		purple_debug_error("yahoo", "Unrecognized yahoo file transfer mode and stage (ymsg15):%d,%d\n", purple_xfer_get_type(xfer), xd->status_15);
+		return;
+	}
+
+}
+
+
+static void yahoo_xfer_connected_15(gpointer data, gint source, const gchar *error_message)
+{
+	PurpleXfer *xfer;
+	struct yahoo_xfer_data *xd;
+	PurpleAccount *account;
+	struct yahoo_data* yd;
+
+	if (!(xfer = data))
+		return;
+	if (!(xd = xfer->data))
+		return;
+	yd = xd->gc->proto_data;
+	account = purple_connection_get_account(xd->gc);
+	if ((source < 0) || (xd->path == NULL) || (xd->host == NULL)) {
+		purple_xfer_error(PURPLE_XFER_RECEIVE, purple_xfer_get_account(xfer),
+			xfer->who, _("Unable to connect."));
+		purple_xfer_cancel_remote(xfer);
+		return;
+	}
+	/* The first time we get here, assemble the tx buffer */
+	if (xd->txbuflen == 0)
+	{
+		gchar* cookies;
+		cookies = yahoo_get_cookies(xd->gc);
+		if(purple_xfer_get_type(xfer) == PURPLE_XFER_SEND && xd->status_15 == ACCEPTED)
+		{
+			xd->txbuf = g_strdup_printf("POST /relay?token=%s&sender=%s&recver=%s HTTP/1.1\r\nCookie:%s\r\nUser-Agent: Mozilla/4.0 (compatible; MSIE 5.5)\r\nHost: %s\r\nContent-Length: %ld\r\nCache-Control: no-cache\r\n\r\n",
+										purple_url_encode(xd->xfer_idstring_for_relay),
+										purple_normalize(account, purple_account_get_username(account)),
+										xfer->who,
+										cookies,
+										xd->host,
+										(long int)xfer->size);
+		}
+		else if(purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE && xd->status_15 == STARTED)
+		{	
+			xd->txbuf = g_strdup_printf("HEAD /relay?token=%s&sender=%s&recver=%s HTTP/1.1\r\nAccept:*/*\r\nCookie:%s\r\nUser-Agent: Mozilla/4.0 (compatible; MSIE 5.5)\r\nHost:%s\r\nContent-Length: 0\r\nCache-Control: no-cache\r\n\r\n",
+										purple_url_encode(xd->xfer_idstring_for_relay),
+										purple_normalize(account, purple_account_get_username(account)),
+										xfer->who,
+										cookies,
+										xd->host);
+		}
+		else if(purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE && xd->status_15 == HEAD_REPLY_RECEIVED)
+		{
+			xd->txbuf = g_strdup_printf("GET /relay?token=%s&sender=%s&recver=%s HTTP/1.1\r\nCookie:%s\r\nUser-Agent: Mozilla/4.0 (compatible; MSIE 5.5)\r\nHost:%s\r\nConnection: Keep-Alive\r\n\r\n",
+										purple_url_encode(xd->xfer_idstring_for_relay),
+										purple_normalize(account, purple_account_get_username(account)),
+										xfer->who,
+										cookies,
+										xd->host);
+		}
+		else
+		{
+			purple_debug_error("yahoo", "Unrecognized yahoo file transfer mode and stage (ymsg15):%d,%d\n", purple_xfer_get_type(xfer), xd->status_15);
+			g_free(cookies);
+			return;
+		}
+		xd->txbuflen = strlen(xd->txbuf);
+		xd->txbuf_written = 0;
+		g_free(cookies);
+	}
+
+	if (!xd->tx_handler)
+	{
+		xd->tx_handler = purple_input_add(source, PURPLE_INPUT_WRITE,
+			yahoo_xfer_send_cb_15, xfer);
+		yahoo_xfer_send_cb_15(xfer, source, PURPLE_INPUT_WRITE);
+	}
+}
+
+void yahoo_process_filetrans_15(PurpleConnection *gc, struct yahoo_packet *pkt)
+{
+	char *from = NULL;
+	char *to = NULL;
+	char *imv = NULL;
+	long val_222 = 0L;
+	PurpleXfer *xfer;
+	struct yahoo_data *yd;
+	struct yahoo_xfer_data *xfer_data;
+	char *service = NULL;
+	char *filename = NULL;
+	char *xfer_peer_idstring = NULL;
+	unsigned long filesize = 0L;
+	GSList *l;
+	GSList *filename_list = NULL;
+	GSList *size_list = NULL;
+	int nooffiles = 0;
+	
+	yd = gc->proto_data;
+
+	for (l = pkt->hash; l; l = l->next) {
+		struct yahoo_pair *pair = l->data;
+
+		switch (pair->key) {
+		case 4:
+			from = pair->value;
+			break;
+		case 5:
+			to = pair->value;
+			break;
+		case 265:
+			xfer_peer_idstring = pair->value;
+			break;
+		case 27:
+			filename_list = g_slist_prepend(filename_list, g_strdup(pair->value));
+			nooffiles++;
+			break;
+		case 28:
+			size_list = g_slist_prepend(size_list, g_strdup(pair->value));
+			break;
+		case 222:
+			val_222 = atol(pair->value);
+			/* 1=send, 2=cancel, 3=accept, 4=reject */ 
+			break;
+
+		/*check for p2p and imviron .... not sure it comes by this service packet. Since it was bundled with filexfer in old ymsg version, still keeping it.*/
+		case 49:
+			service = pair->value;
+			break;
+		case 63:
+			imv = pair->value;
+			break;
+		/*end check*/
+
+		}
+	}
+	if(!xfer_peer_idstring)
+		return;
+
+	if(val_222 == 2 || val_222 == 4)
+	{
+		xfer = g_hash_table_lookup(yd->xfer_peer_idstring_map,
+								   xfer_peer_idstring);
+		if(!xfer) return;
+		purple_xfer_cancel_remote(xfer);
+		return;
+	}
+	if(val_222 == 3)
+	{
+		xfer = g_hash_table_lookup(yd->xfer_peer_idstring_map,
+								   xfer_peer_idstring);
+		if(!xfer)
+			return;
+		/*
+		*	In the file trans info packet tht we must reply with , we are supposed to mention the ip address...
+		*	purple connect does not give me a way of finding the ip address...
+		*	so, purple dnsquery is used... but retries, trying with next ip address etc. is not implemented..TODO
+		*/
+		if (yd->jp)
+		{
+			purple_dnsquery_a(YAHOOJP_XFER_RELAY_HOST, YAHOOJP_XFER_RELAY_PORT, yahoo_xfer_dns_connected_15, xfer);
+		}
+		else
+		{
+			purple_dnsquery_a(YAHOO_XFER_RELAY_HOST, YAHOO_XFER_RELAY_PORT, yahoo_xfer_dns_connected_15, xfer);
+		}
+		return;
+	}
+
+	/*processing for p2p and imviron .... not sure it comes by this service packet. Since it was bundled with filexfer in old ymsg version, still keeping it.*/
+	/*
+	* The remote user has changed their IMVironment.  We
+	* record it for later use.
+	*/
+	if (from && imv && service && (strcmp("IMVIRONMENT", service) == 0)) {
+		g_hash_table_replace(yd->imvironments, g_strdup(from), g_strdup(imv));
+		return;
+	}
+	
+	if (pkt->service == YAHOO_SERVICE_P2PFILEXFER) {
+		if (service && (strcmp("FILEXFER", service) != 0)) {
+			purple_debug_misc("yahoo", "unhandled service 0x%02x\n", pkt->service);
+			return;
+		}
+	}
+	/*end processing*/
+
+	if(!filename_list)
+		return;
+	/* have to change list into order in which client at other end sends */
+	filename_list = g_slist_reverse(filename_list);
+	size_list = g_slist_reverse(size_list);
+	filename = filename_list->data;
+	filesize = atol(size_list->data);
+
+	if(!from) return;
+	xfer_data = g_new0(struct yahoo_xfer_data, 1);
+	xfer_data->version = 15;
+	xfer_data->firstoflist = TRUE;
+	xfer_data->gc = gc;
+	xfer_data->xfer_peer_idstring = g_strdup(xfer_peer_idstring);
+	xfer_data->filename_list = filename_list;
+	xfer_data->size_list = size_list;
+	
+	/* Build the file transfer handle. */
+	xfer = purple_xfer_new(gc->account, PURPLE_XFER_RECEIVE, from);
+	xfer->message = NULL;
+
+	if (xfer)
+	{
+		/* Set the info about the incoming file. */
+		char *utf8_filename = yahoo_string_decode(gc, filename, TRUE);
+		purple_xfer_set_filename(xfer, utf8_filename);
+		g_free(utf8_filename);
+		purple_xfer_set_size(xfer, filesize);
+
+		xfer->data = xfer_data;
+
+
+		/* Setup our I/O op functions */
+		purple_xfer_set_init_fnc(xfer,        yahoo_xfer_init_15);
+		purple_xfer_set_start_fnc(xfer,       yahoo_xfer_start);
+		purple_xfer_set_end_fnc(xfer,         yahoo_xfer_end);
+		purple_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send);
+		purple_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv);
+		purple_xfer_set_read_fnc(xfer,        yahoo_xfer_read);
+		purple_xfer_set_write_fnc(xfer,       yahoo_xfer_write);
+		purple_xfer_set_request_denied_fnc(xfer,yahoo_xfer_cancel_recv);
+
+		g_hash_table_insert(yd->xfer_peer_idstring_map,
+							xfer_data->xfer_peer_idstring,
+							xfer);
+		
+		if(nooffiles > 1) {
+			gchar* message;
+			message = g_strdup_printf(_("%s is trying to send you a group of %d files.\n"), xfer->who, nooffiles);
+			purple_xfer_conversation_write(xfer, message, FALSE);
+			g_free(message);
+		}
+		/* Now perform the request */
+		purple_xfer_request(xfer);
+	}
+}
+
+void yahoo_process_filetrans_info_15(PurpleConnection *gc, struct yahoo_packet *pkt)
+{
+	char *from = NULL;
+	char *to = NULL;
+	char *url = NULL;
+	long val_249 = 0;
+	long val_66 = 0;
+	PurpleXfer *xfer;
+	struct yahoo_data *yd;
+	struct yahoo_xfer_data *xfer_data;
+	char *filename = NULL;
+	char *xfer_peer_idstring = NULL;
+	char *xfer_idstring_for_relay = NULL;
+	GSList *l;
+	struct yahoo_packet *pkt_to_send;
+	PurpleAccount *account;
+
+	yd = gc->proto_data;
+
+	for (l = pkt->hash; l; l = l->next) {
+		struct yahoo_pair *pair = l->data;
+
+		switch (pair->key) {
+		case 4:
+			from = pair->value;
+			break;
+		case 5:
+			to = pair->value;
+			break;
+		case 265:
+			xfer_peer_idstring = pair->value;
+			break;
+		case 27:
+			filename = pair->value;
+			break;
+		case 66:
+			val_66 = strtol(pair->value, NULL, 10);
+			break;
+		case 249:
+			val_249 = strtol(pair->value, NULL, 10); /*
+					* really pissed off with this- i hv seen 2 occurences of this
+					* being 1(its normally 3) - and in those cases, the url
+					* format and corresponding processing seems to be different
+					* (i havent tested - couldnt reproduce a 1), although i 
+					* guess its easier.
+					*/
+			break;
+		case 250:
+			url = pair->value;
+			break;
+		case 251:
+			xfer_idstring_for_relay = pair->value;
+			break;
+		}
+	}
+
+	if(!xfer_peer_idstring)
+		return;
+
+	xfer = g_hash_table_lookup(yd->xfer_peer_idstring_map, xfer_peer_idstring);
+
+	if(!xfer) return;
+
+	if(val_66==-1)
+	{
+		purple_xfer_cancel_remote(xfer);
+		return;
+	}
+
+	xfer_data = xfer->data;
+
+	xfer_data->info_val_249 = val_249;
+	xfer_data->xfer_idstring_for_relay = g_strdup(xfer_idstring_for_relay);
+	if (!purple_url_parse(url, &(xfer_data->host), &(xfer_data->port), &(xfer_data->path), NULL, NULL)) {
+		purple_xfer_cancel_remote(xfer);
+		return;
+	}
+
+	account = purple_connection_get_account(xfer_data->gc);
+
+	pkt_to_send = yahoo_packet_new(YAHOO_SERVICE_FILETRANS_ACC_15,
+		YAHOO_STATUS_AVAILABLE, yd->session_id);
+
+	yahoo_packet_hash(pkt_to_send, "ssssisi",
+		1, purple_normalize(account, purple_account_get_username(account)),
+		5, xfer->who,
+		265, xfer_data->xfer_peer_idstring,
+		27, xfer->filename,
+		249, xfer_data->info_val_249,
+		251, xfer_data->xfer_idstring_for_relay,
+		222, 3);
+
+	yahoo_packet_send_and_free(pkt_to_send, yd);
+	if (purple_proxy_connect(NULL, account, xfer_data->host, xfer_data->port,
+		yahoo_xfer_connected_15, xfer) == NULL) {
+		purple_notify_error(gc, NULL, _("File Transfer Failed"),
+			_("Unable to establish file descriptor."));
+		purple_xfer_cancel_remote(xfer);
+		}
+
+}
+/*TODO: Check filename etc. No probs till some hacker comes in the way*/
+void yahoo_process_filetrans_acc_15(PurpleConnection *gc, struct yahoo_packet *pkt)
+{
+	gchar *xfer_peer_idstring = NULL;
+	gchar *xfer_idstring_for_relay = NULL;
+	PurpleXfer *xfer;
+	struct yahoo_data *yd;
+	struct yahoo_xfer_data *xfer_data;
+	GSList *l;
+	PurpleAccount *account;
+	long val_66 = 0;
+
+	yd = gc->proto_data;
+	for (l = pkt->hash; l; l = l->next) {
+		struct yahoo_pair *pair = l->data;
+
+		switch (pair->key) {
+		case 251:
+			xfer_idstring_for_relay = pair->value;
+			break;
+		case 265:
+			xfer_peer_idstring = pair->value;
+			break;
+		case 66:
+			val_66 = atol(pair->value);
+		}
+	}
+
+	xfer = g_hash_table_lookup(yd->xfer_peer_idstring_map, xfer_peer_idstring);
+	if(!xfer) return;
+
+	if(val_66 == -1 || !(xfer_idstring_for_relay))
+	{
+		purple_xfer_cancel_remote(xfer);
+		return;
+	}
+
+	xfer_data = xfer->data;
+	xfer_data->xfer_idstring_for_relay = g_strdup(xfer_idstring_for_relay);
+	xfer_data->status_15 = ACCEPTED;
+	account = purple_connection_get_account(gc);
+
+	if (purple_proxy_connect(NULL, account, xfer_data->host, xfer_data->port,
+		yahoo_xfer_connected_15, xfer) == NULL)
+	{
+		purple_notify_error(gc, NULL, _("File Transfer Failed"),_("Unable to connect"));
+			purple_xfer_cancel_remote(xfer);
+	}
+}
--- a/libpurple/protocols/yahoo/yahoo_filexfer.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.h	Wed Jan 30 10:53:22 2008 +0000
@@ -30,16 +30,6 @@
 void yahoo_process_p2pfilexfer( PurpleConnection *gc, struct yahoo_packet *pkt );
 
 /**
- * Process ymsg version 7 file receive invites.
- */
-void yahoo_process_y7_filetransfer(PurpleConnection *gc, struct yahoo_packet *pkt);
-
-/**
- * Process ymsg version 7 file receive connection setups.
- */
-void yahoo_process_y7_filetransfer_info(PurpleConnection *gc, struct yahoo_packet *pkt);
-
-/**
  * Process ymsg file receive invites.
  */
 void yahoo_process_filetransfer(PurpleConnection *gc, struct yahoo_packet *pkt);
@@ -61,4 +51,8 @@
  */
 void yahoo_send_file(PurpleConnection *gc, const char *who, const char *file);
 
+void yahoo_process_filetrans_15(PurpleConnection *gc, struct yahoo_packet *pkt);
+void yahoo_process_filetrans_info_15(PurpleConnection *gc, struct yahoo_packet *pkt);
+void yahoo_process_filetrans_acc_15(PurpleConnection *gc, struct yahoo_packet *pkt);
+
 #endif
--- a/libpurple/protocols/yahoo/yahoo_friend.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo_friend.h	Wed Jan 30 10:53:22 2008 +0000
@@ -48,6 +48,7 @@
 	gboolean bicon_sent_request;
 	YahooPresenceVisibility presence;
 	int protocol; /* 1=LCS, 2=MSN*/
+	long int version_id;
 } YahooFriend;
 
 YahooFriend *yahoo_friend_find(PurpleConnection *gc, const char *name);
--- a/libpurple/protocols/yahoo/yahoo_packet.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo_packet.h	Wed Jan 30 10:53:22 2008 +0000
@@ -99,12 +99,12 @@
 	YAHOO_SERVICE_VERIFY_ID_EXISTS = 0xc8,
 	YAHOO_SERVICE_AUDIBLE = 0xd0,
 	YAHOO_SERVICE_AUTH_REQ_15 = 0xd6,
-	YAHOO_SERVICE_Y7_FILETRANSFER = 0xdc,
-	YAHOO_SERVICE_Y7_FILETRANSFER_INFO = 0xdd,
-	YAHOO_SERVICE_Y7_FILETRANSFER_ACCEPT = 0xde,
 	YAHOO_SERVICE_CHGRP_15 = 0xe7,
 	YAHOO_SERVICE_STATUS_15 = 0xf0,
 	YAHOO_SERVICE_LIST_15 = 0xf1,
+	YAHOO_SERVICE_FILETRANS_15 = 0xdc,
+	YAHOO_SERVICE_FILETRANS_INFO_15 = 0xdd,
+	YAHOO_SERVICE_FILETRANS_ACC_15 = 0xde,
 	YAHOO_SERVICE_WEBLOGIN = 0x0226,
 	YAHOO_SERVICE_SMS_MSG = 0x02ea
 };
--- a/libpurple/protocols/yahoo/yahoochat.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoochat.c	Wed Jan 30 10:53:22 2008 +0000
@@ -1461,28 +1461,30 @@
 
 PurpleRoomlist *yahoo_roomlist_get_list(PurpleConnection *gc)
 {
-	struct yahoo_roomlist *yrl;
+	PurpleAccount *account;
 	PurpleRoomlist *rl;
-	const char *rll;
+	PurpleRoomlistField *f;
+	GList *fields = NULL;
+	struct yahoo_roomlist *yrl;
+	const char *rll, *rlurl;
 	char *url;
-	GList *fields = NULL;
-	PurpleRoomlistField *f;
 
-	rll = purple_account_get_string(purple_connection_get_account(gc),
-								  "room_list_locale", YAHOO_ROOMLIST_LOCALE);
+	account = purple_connection_get_account(gc);
 
-	if (rll != NULL && *rll != '\0') {
-		url = g_strdup_printf("%s?chatcat=0&intl=%s",
-	        purple_account_get_string(purple_connection_get_account(gc),
-	        "room_list", YAHOO_ROOMLIST_URL), rll);
-	} else {
-		url = g_strdup_printf("%s?chatcat=0",
-	        purple_account_get_string(purple_connection_get_account(gc),
-	        "room_list", YAHOO_ROOMLIST_URL));
+	/* for Yahoo Japan, it appears there is only one valid URL and locale */
+	if(purple_account_get_bool(account, "yahoojp", FALSE)) {
+		rll = YAHOOJP_ROOMLIST_LOCALE;
+		rlurl = YAHOOJP_ROOMLIST_URL;
+	}
+	else { /* but for the rest of the world that isn't the case */
+		rll = purple_account_get_string(account, "room_list_locale", YAHOO_ROOMLIST_LOCALE);
+		rlurl = purple_account_get_string(account, "room_list", YAHOO_ROOMLIST_URL);
 	}
 
+	url = g_strdup_printf("%s?chatcat=0&intl=%s", rlurl, rll);
+
 	yrl = g_new0(struct yahoo_roomlist, 1);
-	rl = purple_roomlist_new(purple_connection_get_account(gc));
+	rl = purple_roomlist_new(account);
 	yrl->list = rl;
 
 	purple_url_parse(url, &(yrl->host), NULL, &(yrl->path), NULL, NULL);
@@ -1508,7 +1510,7 @@
 
 	purple_roomlist_set_fields(rl, fields);
 
-	if (purple_proxy_connect(NULL, purple_connection_get_account(gc), yrl->host, 80,
+	if (purple_proxy_connect(NULL, account, yrl->host, 80,
 	                       yahoo_roomlist_got_connected, yrl) == NULL)
 	{
 		purple_notify_error(gc, NULL, _("Connection problem"), _("Unable to fetch room list."));
--- a/libpurple/protocols/zephyr/ZSendList.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/zephyr/ZSendList.c	Wed Jan 30 10:53:22 2008 +0000
@@ -24,7 +24,7 @@
     char *list[];
     int nitems;
     Z_AuthProc cert_routine;
-    Code_t (*send_routine)(void);
+    Code_t (*send_routine)();
 {
     Code_t retval;
     ZNotice_t newnotice;
--- a/libpurple/protocols/zephyr/ZSendNot.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/zephyr/ZSendNot.c	Wed Jan 30 10:53:22 2008 +0000
@@ -20,7 +20,7 @@
 Code_t ZSrvSendNotice(notice, cert_routine, send_routine)
     ZNotice_t *notice;
     Z_AuthProc cert_routine;
-    Code_t (*send_routine)(void);
+    Code_t (*send_routine)();
 {    
     Code_t retval;
     ZNotice_t newnotice;
--- a/libpurple/protocols/zephyr/zephyr.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/zephyr/zephyr.c	Wed Jan 30 10:53:22 2008 +0000
@@ -156,13 +156,20 @@
 #endif
 
 static Code_t zephyr_subscribe_to(zephyr_account* zephyr, char* class, char *instance, char *recipient, char* galaxy) {
+	size_t result;
+	Code_t ret_val = -1;
 
 	if (use_tzc(zephyr)) {
 		/* ((tzcfodder . subscribe) ("class" "instance" "recipient")) */
 		gchar *zsubstr = g_strdup_printf("((tzcfodder . subscribe) (\"%s\" \"%s\" \"%s\"))\n",class,instance,recipient);
-		write(zephyr->totzc[ZEPHYR_FD_WRITE],zsubstr,strlen(zsubstr));
+		size_t len = strlen(zsubstr);
+		result = write(zephyr->totzc[ZEPHYR_FD_WRITE],zsubstr,len);
+		if (result != len) {
+			purple_debug_error("zephyr", "Unable to write a message: %s\n", g_strerror(errno));
+		} else {
+			ret_val = ZERR_NONE;
+		}
 		g_free(zsubstr);
-		return ZERR_NONE;
 	}
 	else {
 		if (use_zeph02(zephyr)) {
@@ -170,13 +177,10 @@
 			sub.zsub_class = class;
 			sub.zsub_classinst = instance;
 			sub.zsub_recipient = recipient; 
-			return ZSubscribeTo(&sub,1,0);
-		} else {
-			/* This should not happen */
-			return -1;
+			ret_val = ZSubscribeTo(&sub,1,0);
 		}
 	}
-	return -1;
+	return ret_val;
 }
 
 char *local_zephyr_normalize(zephyr_account* zephyr,const char *);
@@ -1373,7 +1377,11 @@
 					} else 
 						if (use_tzc(zephyr)) {
 							gchar *zlocstr = g_strdup_printf("((tzcfodder . zlocate) \"%s\")\n",chk);
-							write(zephyr->totzc[ZEPHYR_FD_WRITE],zlocstr,strlen(zlocstr));
+							size_t len = strlen(zlocstr);
+							size_t result = write(zephyr->totzc[ZEPHYR_FD_WRITE],zlocstr,len);
+							if (result != len) {
+								purple_debug_error("zephyr", "Unable to write a message: %s\n", g_strerror(errno));
+							}
 							g_free(zlocstr);
 						}
 				}
@@ -2193,6 +2201,8 @@
 	html_buf2 = purple_unescape_html(html_buf);
 
 	if(use_tzc(zephyr)) {
+		size_t len;
+		size_t result;
 		char* zsendstr;
 		/* CMU cclub tzc doesn't grok opcodes for now  */
 		char* tzc_sig = zephyr_tzc_escape_msg(sig);
@@ -2200,7 +2210,14 @@
 		zsendstr = g_strdup_printf("((tzcfodder . send) (class . \"%s\") (auth . t) (recipients (\"%s\" . \"%s\")) (message . (\"%s\" \"%s\"))	) \n",
 					   zclass, instance, recipient, tzc_sig, tzc_body);
 		/*		fprintf(stderr,"zsendstr = %s\n",zsendstr); */
-		write(zephyr->totzc[ZEPHYR_FD_WRITE],zsendstr,strlen(zsendstr));
+		len = strlen(zsendstr);
+		result = write(zephyr->totzc[ZEPHYR_FD_WRITE], zsendstr, len);
+		if (result != len) {
+			g_free(zsendstr);
+			g_free(html_buf2);
+			g_free(html_buf);
+			return errno;
+		}
 		g_free(zsendstr);
 	} else if (use_zeph02(zephyr)) {
 		ZNotice_t notice;
@@ -2221,6 +2238,9 @@
 		purple_debug_info("zephyr","About to send notice\n");
 		if (! ZSendNotice(&notice, ZAUTH) == ZERR_NONE) {
 			/* XXX handle errors here */
+			g_free(buf);
+			g_free(html_buf2);
+			g_free(html_buf);
 			return 0;
 		}
 		purple_debug_info("zephyr","notice sent\n");
@@ -2257,7 +2277,7 @@
 	ZAsyncLocateData_t ald;
 	zephyr_account *zephyr = gc->proto_data;
 	gchar* normalized_who = local_zephyr_normalize(zephyr,who);
-	
+
 	if (use_zeph02(zephyr)) {
 		if (ZRequestLocations(normalized_who, &ald, UNACKED, ZAUTH) == ZERR_NONE) {
 			zephyr->pending_zloc_names = g_list_append(zephyr->pending_zloc_names,
@@ -2266,14 +2286,22 @@
 			/* XXX deal with errors somehow */
 		}
 	} else if (use_tzc(zephyr)) {
+		size_t len;
+		size_t result;
 		char* zlocstr = g_strdup_printf("((tzcfodder . zlocate) \"%s\")\n",normalized_who);
 		zephyr->pending_zloc_names = g_list_append(zephyr->pending_zloc_names, g_strdup(normalized_who));
-		write(zephyr->totzc[ZEPHYR_FD_WRITE],zlocstr,strlen(zlocstr));
+		len = strlen(zlocstr);
+		result = write(zephyr->totzc[ZEPHYR_FD_WRITE],zlocstr,len);
+		if (result != len) {
+			purple_debug_error("zephyr", "Unable to write a message: %s\n", g_strerror(errno));
+		}
 		g_free(zlocstr);
 	}
 }
 
 static void zephyr_set_status(PurpleAccount *account, PurpleStatus *status) {
+	size_t len;
+	size_t result;
 	zephyr_account *zephyr = purple_account_get_connection(account)->proto_data;
 	PurpleStatusPrimitive primitive = purple_status_type_get_primitive(purple_status_get_type(status));
 
@@ -2291,7 +2319,11 @@
 		}
 		else {
 			char *zexpstr = g_strdup_printf("((tzcfodder . set-location) (hostname . \"%s\") (exposure . \"%s\"))\n",zephyr->ourhost,zephyr->exposure);
-			write(zephyr->totzc[ZEPHYR_FD_WRITE],zexpstr,strlen(zexpstr));
+			len = strlen(zexpstr);
+			result = write(zephyr->totzc[ZEPHYR_FD_WRITE],zexpstr,len);
+			if (result != len) {
+				purple_debug_error("zephyr", "Unable to write message: %s\n", g_strerror(errno));
+			}
 			g_free(zexpstr);
 		}
 	} 
@@ -2301,7 +2333,11 @@
 			ZSetLocation(EXPOSE_OPSTAFF);
 		} else {
 			char *zexpstr = g_strdup_printf("((tzcfodder . set-location) (hostname . \"%s\") (exposure . \"%s\"))\n",zephyr->ourhost,EXPOSE_OPSTAFF);
-			write(zephyr->totzc[ZEPHYR_FD_WRITE],zexpstr,strlen(zexpstr));
+			len = strlen(zexpstr);
+			result = write(zephyr->totzc[ZEPHYR_FD_WRITE],zexpstr,len);
+			if (result != len) {
+				purple_debug_error("zephyr", "Unable to write message: %s\n", g_strerror(errno));
+			}
 			g_free(zexpstr);
 		}
 	}
--- a/libpurple/protocols/zephyr/zephyr.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/protocols/zephyr/zephyr.h	Wed Jan 30 10:53:22 2008 +0000
@@ -163,9 +163,9 @@
 Code_t ZReadAscii16 ZP((char *, int, unsigned short *));
 Code_t ZSendPacket ZP((char*, int, int));
 Code_t ZSendList ZP((ZNotice_t*, char *[], int, Z_AuthProc));
-Code_t ZSrvSendList ZP((ZNotice_t*, char*[], int, Z_AuthProc, Code_t (*)(void)));
+Code_t ZSrvSendList ZP((ZNotice_t*, char*[], int, Z_AuthProc, Code_t (*)()));
 Code_t ZSendNotice ZP((ZNotice_t *, Z_AuthProc));
-Code_t ZSrvSendNotice ZP((ZNotice_t*, Z_AuthProc, Code_t (*)(void)));
+Code_t ZSrvSendNotice ZP((ZNotice_t*, Z_AuthProc, Code_t (*)()));
 Code_t ZFormatNotice ZP((ZNotice_t*, char**, int*, Z_AuthProc));
 Code_t ZFormatSmallNotice ZP((ZNotice_t*, ZPacket_t, int*, Z_AuthProc));
 Code_t ZFormatRawNoticeList ZP((ZNotice_t *notice, char *list[], int nitems,
--- a/libpurple/prpl.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/prpl.c	Wed Jan 30 10:53:22 2008 +0000
@@ -29,6 +29,107 @@
 #include "util.h"
 
 /**************************************************************************/
+/** @name Attention Type API                                              */
+/**************************************************************************/
+PurpleAttentionType *
+purple_attention_type_new(const char *ulname, const char *name,
+						const char *inc_desc, const char *out_desc)
+{
+	PurpleAttentionType *attn = g_new0(PurpleAttentionType, 1);
+
+	purple_attention_type_set_name(attn, name);
+	purple_attention_type_set_incoming_desc(attn, inc_desc);
+	purple_attention_type_set_outgoing_desc(attn, out_desc);
+	purple_attention_type_set_unlocalized_name(attn, ulname);
+
+	return attn;
+}
+
+
+void
+purple_attention_type_set_name(PurpleAttentionType *type, const char *name)
+{
+	g_return_if_fail(type != NULL);
+
+	type->name = name;
+}
+
+void
+purple_attention_type_set_incoming_desc(PurpleAttentionType *type, const char *desc)
+{
+	g_return_if_fail(type != NULL);
+
+	type->incoming_description = desc;
+}
+
+void
+purple_attention_type_set_outgoing_desc(PurpleAttentionType *type, const char *desc)
+{
+	g_return_if_fail(type != NULL);
+
+	type->outgoing_description = desc;
+}
+
+void
+purple_attention_type_set_icon_name(PurpleAttentionType *type, const char *name)
+{
+	g_return_if_fail(type != NULL);
+	
+	type->icon_name = name;
+}
+
+void
+purple_attention_type_set_unlocalized_name(PurpleAttentionType *type, const char *ulname)
+{
+	g_return_if_fail(type != NULL);
+
+	type->unlocalized_name = ulname;
+}
+
+const char *
+purple_attention_type_get_name(const PurpleAttentionType *type)
+{
+	g_return_val_if_fail(type != NULL, NULL);
+
+	return type->name;
+}
+
+const char *
+purple_attention_type_get_incoming_desc(const PurpleAttentionType *type)
+{
+	g_return_val_if_fail(type != NULL, NULL);
+
+	return type->incoming_description;
+}
+
+const char *
+purple_attention_type_get_outgoing_desc(const PurpleAttentionType *type)
+{
+	g_return_val_if_fail(type != NULL, NULL);
+
+	return type->outgoing_description;
+}
+
+const char *
+purple_attention_type_get_icon_name(const PurpleAttentionType *type)
+{
+	g_return_val_if_fail(type != NULL, NULL);
+
+	if(type->icon_name == NULL || *(type->icon_name) == '\0')
+		return NULL;
+
+	return type->icon_name;
+}
+
+const char *
+purple_attention_type_get_unlocalized_name(const PurpleAttentionType *type)
+{
+	g_return_val_if_fail(type != NULL, NULL);
+
+	return type->unlocalized_name;
+}
+
+/**************************************************************************/
 /** @name Protocol Plugin API  */
 /**************************************************************************/
 void
--- a/libpurple/prpl.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/prpl.h	Wed Jan 30 10:53:22 2008 +0000
@@ -99,9 +99,9 @@
 	const char *incoming_description;  /**< Shown when sent */
 	const char *outgoing_description;  /**< Shown when receied */
 	const char *icon_name;             /**< Icon to display (optional) */
+	const char *unlocalized_name;      /**< Unlocalized name for UIs needing it */
 
 	/* Reserved fields for future purposes */
-	gpointer _reserved1;
 	gpointer _reserved2;
 	gpointer _reserved3;
 	gpointer _reserved4;
@@ -412,6 +412,127 @@
 #endif
 
 /**************************************************************************/
+/** @name Attention Type API                                              */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Creates a new #PurpleAttentionType object and sets its mandatory parameters.
+ *
+ * @param ulname A non-localized string that can be used by UIs in need of such
+ *               non-localized strings.  This should be the same as @a name,
+ *               without localization.
+ * @param name A localized string that the UI may display for the event. This
+ *             should be the same string as @a ulname, with localization.
+ * @param inc_desc A localized description shown when the event is received.
+ * @param out_desc A localized description shown when the event is sent.
+ * @return A pointer to the new object.
+ * @since 2.4.0
+ */
+PurpleAttentionType *purple_attention_type_new(const char *ulname, const char *name,
+								const char *inc_desc, const char *out_desc);
+
+/**
+ * Sets the displayed name of the attention-demanding event.
+ *
+ * @param type The attention type.
+ * @param name The localized name that will be displayed by UIs. This should be
+ *             the same string given as the unlocalized name, but with
+ *             localization.
+ * @since 2.4.0
+ */
+void purple_attention_type_set_name(PurpleAttentionType *type, const char *name);
+
+/**
+ * Sets the description of the attention-demanding event shown in  conversations
+ * when the event is received.
+ *
+ * @param type The attention type.
+ * @param desc The localized description for incoming events.
+ * @since 2.4.0
+ */
+void purple_attention_type_set_incoming_desc(PurpleAttentionType *type, const char *desc);
+
+/**
+ * Sets the description of the attention-demanding event shown in conversations
+ * when the event is sent.
+ *
+ * @param type The attention type.
+ * @param desc The localized description for outgoing events.
+ * @since 2.4.0
+ */
+void purple_attention_type_set_outgoing_desc(PurpleAttentionType *type, const char *desc);
+
+/**
+ * Sets the name of the icon to display for the attention event; this is optional.
+ *
+ * @param type The attention type.
+ * @param name The icon's name.
+ * @note Icons are optional for attention events.
+ * @since 2.4.0
+ */
+void purple_attention_type_set_icon_name(PurpleAttentionType *type, const char *name);
+
+/**
+ * Sets the unlocalized name of the attention event; some UIs may need this,
+ * thus it is required.
+ *
+ * @param type The attention type.
+ * @param ulname The unlocalized name.  This should be the same string given as
+ *               the localized name, but without localization.
+ * @since 2.4.0
+ */
+void purple_attention_type_set_unlocalized_name(PurpleAttentionType *type, const char *ulname);
+
+/**
+ * Get the attention type's name as displayed by the UI.
+ *
+ * @param type The attention type.
+ * @return The name.
+ * @since 2.4.0
+ */
+const char *purple_attention_type_get_name(const PurpleAttentionType *type);
+
+/**
+ * Get the attention type's description shown when the event is received.
+ *
+ * @param type The attention type.
+ * @return The description.
+ * @since 2.4.0
+ */
+const char *purple_attention_type_get_incoming_desc(const PurpleAttentionType *type);
+
+/**
+ * Get the attention type's description shown when the event is sent.
+ *
+ * @param type The attention type.
+ * @return The description.
+ * @since 2.4.0
+ */
+const char *purple_attention_type_get_outgoing_desc(const PurpleAttentionType *type);
+
+/**
+ * Get the attention type's icon name.
+ *
+ * @param type The attention type.
+ * @return The icon name or @c NULL if unset/empty.
+ * @note Icons are optional for attention events.
+ * @since 2.4.0
+ */
+const char *purple_attention_type_get_icon_name(const PurpleAttentionType *type);
+
+/**
+ * Get the attention type's unlocalized name; this is useful for some UIs.
+ *
+ * @param type The attention type
+ * @return The unlocalized name.
+ * @since 2.4.0
+ */
+const char *purple_attention_type_get_unlocalized_name(const PurpleAttentionType *type);
+
+/*@}*/
+
+/**************************************************************************/
 /** @name Protocol Plugin API                                             */
 /**************************************************************************/
 /*@{*/
--- a/libpurple/request.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/request.h	Wed Jan 30 10:53:22 2008 +0000
@@ -1318,6 +1318,8 @@
  * @param secondary      Secondary information, or @c NULL if there is none.
  * @param default_action The default action, zero-indexed; if the third action
  *                       supplied should be the default, supply <tt>2</tt>.
+ *                       The should be the action that users are most likely
+ *                       to select.
  * @param account        The #PurpleAccount associated with this request, or @c
  *                       NULL if none is.
  * @param who            The username of the buddy associated with this request,
@@ -1356,6 +1358,8 @@
  * @param secondary      Secondary information, or @c NULL if there is none.
  * @param default_action The default action, zero-indexed; if the third action
  *                       supplied should be the default, supply <tt>2</tt>.
+ *                       The should be the action that users are most likely
+ *                       to select.
  * @param account        The #PurpleAccount associated with this request, or @c
  *                       NULL if none is.
  * @param who            The username of the buddy associated with this request,
--- a/libpurple/stringref.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/stringref.c	Wed Jan 30 10:53:22 2008 +0000
@@ -31,6 +31,7 @@
 #include <stdarg.h>
 
 #include "debug.h"
+#include "eventloop.h"
 #include "stringref.h"
 
 /**
@@ -87,7 +88,7 @@
 	newref->ref = 0x80000000;
 
 	if (gclist == NULL)
-		g_idle_add(gs_idle_cb, NULL);
+		purple_timeout_add(0, gs_idle_cb, NULL);
 	gclist = g_list_prepend(gclist, newref);
 
 	return newref;
--- a/libpurple/tests/test_util.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/tests/test_util.c	Wed Jan 30 10:53:22 2008 +0000
@@ -71,6 +71,14 @@
 START_TEST(test_util_email_is_valid)
 {
 	fail_unless(purple_email_is_valid("purple-devel@lists.sf.net"));
+	fail_if(purple_email_is_valid("purple-devel@@lists.sf.net"));
+	fail_if(purple_email_is_valid("purple@devel@lists.sf.net"));
+	fail_if(purple_email_is_valid("purple-devel@list..sf.net"));
+	fail_if(purple_email_is_valid("purple-devel"));
+	fail_if(purple_email_is_valid("purple-devel@"));
+	fail_if(purple_email_is_valid("@lists.sf.net"));
+	fail_if(purple_email_is_valid(""));
+	fail_if(purple_email_is_valid("totally bogus"));
 }
 END_TEST
 
--- a/libpurple/util.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/libpurple/util.c	Wed Jan 30 10:53:22 2008 +0000
@@ -1447,7 +1447,6 @@
 				ALLOW_TAG("pre");
 				ALLOW_TAG("q");
 				ALLOW_TAG("span");
-				ALLOW_TAG("strong");
 				ALLOW_TAG("ul");
 
 
@@ -1467,9 +1466,14 @@
 						plain = g_string_append_c(plain, '\n');
 					continue;
 				}
-				if(!g_ascii_strncasecmp(c, "<b>", 3) || !g_ascii_strncasecmp(c, "<bold>", strlen("<bold>"))) {
+				if(!g_ascii_strncasecmp(c, "<b>", 3) || !g_ascii_strncasecmp(c, "<bold>", strlen("<bold>")) || !g_ascii_strncasecmp(c, "<strong>", strlen("<strong>"))) {
 					struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
-					pt->src_tag = *(c+2) == '>' ? "b" : "bold";
+					if (*(c+2) == '>')
+						pt->src_tag = "b";
+					else if (*(c+2) == 'o')
+						pt->src_tag = "bold";
+					else
+						pt->src_tag = "strong";
 					pt->dest_tag = "span";
 					tags = g_list_prepend(tags, pt);
 					c = strchr(c, '>') + 1;
@@ -4631,6 +4635,68 @@
 #endif /* !_WIN32 */
 }
 
+static void
+set_status_with_attrs(PurpleStatus *status, ...)
+{
+	va_list args;
+	va_start(args, status);
+	purple_status_set_active_with_attrs(status, TRUE, args);
+	va_end(args);
+}
+
+void purple_util_set_current_song(const char *title, const char *artist, const char *album)
+{
+	GList *list = purple_accounts_get_all();
+	for (; list; list = list->next) {
+		PurplePresence *presence;
+		PurpleStatus *tune;
+		PurpleAccount *account = list->data;
+		if (!purple_account_get_enabled(account, purple_core_get_ui()))
+			continue;
+
+		presence = purple_account_get_presence(account);
+		tune = purple_presence_get_status(presence, "tune");
+		if (!tune)
+			continue;
+		if (title) {
+			set_status_with_attrs(tune,
+					PURPLE_TUNE_TITLE, title,
+					PURPLE_TUNE_ARTIST, artist,
+					PURPLE_TUNE_ALBUM, album,
+					NULL);
+		} else {
+			purple_status_set_active(tune, FALSE);
+		}
+	}
+}
+
+char * purple_util_format_song_info(const char *title, const char *artist, const char *album, gpointer unused)
+{
+	GString *string;
+	char *esc;
+
+	if (!title || !*title)
+		return NULL;
+
+	esc = g_markup_escape_text(title, -1);
+	string = g_string_new("");
+	g_string_append_printf(string, "%s", esc);
+	g_free(esc);
+
+	if (artist && *artist) {
+		esc = g_markup_escape_text(artist, -1);
+		g_string_append_printf(string, _(" - %s"), esc);
+		g_free(esc);
+	}
+
+	if (album && *album) {
+		esc = g_markup_escape_text(album, -1);
+		g_string_append_printf(string, _(" (%s)"), esc);
+		g_free(esc);
+	}
+
+	return g_string_free(string, FALSE);
+}
 
 void botch_ucs(gchar *ucs, gssize len)
 {
@@ -4861,57 +4927,3 @@
 	*newlen = bytes;
 	return utf;
 }
-
-void purple_util_set_current_song(const char *title, const char *artist, const char *album)
-{
-	GList *list = purple_accounts_get_all();
-	for (; list; list = list->next) {
-		PurplePresence *presence;
-		PurpleStatus *tune;
-		PurpleAccount *account = list->data;
-		if (!purple_account_get_enabled(account, purple_core_get_ui()))
-			continue;
-
-		presence = purple_account_get_presence(account);
-		tune = purple_presence_get_status(presence, "tune");
-		if (!tune)
-			continue;
-		if (title) {
-			purple_status_set_active(tune, TRUE);
-			purple_status_set_attr_string(tune, PURPLE_TUNE_TITLE, title);
-			purple_status_set_attr_string(tune, PURPLE_TUNE_ARTIST, artist);
-			purple_status_set_attr_string(tune, PURPLE_TUNE_ALBUM, album);
-		} else {
-			purple_status_set_active(tune, FALSE);
-		}
-	}
-}
-
-char * purple_util_format_song_info(const char *title, const char *artist, const char *album, gpointer unused)
-{
-	GString *string;
-	char *esc;
-
-	if (!title)
-		return NULL;
-
-	esc = g_markup_escape_text(title, -1);
-	string = g_string_new("");
-	g_string_append_printf(string, "%s", esc);
-	g_free(esc);
-
-	if (artist) {
-		esc = g_markup_escape_text(artist, -1);
-		g_string_append_printf(string, _(" - %s"), esc);
-		g_free(esc);
-	}
-
-	if (album) {
-		esc = g_markup_escape_text(album, -1);
-		g_string_append_printf(string, _(" (%s)"), esc);
-		g_free(esc);
-	}
-
-	return g_string_free(string, FALSE);
-}
-
--- a/pidgin/gtkaccount.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/pidgin/gtkaccount.c	Wed Jan 30 10:53:22 2008 +0000
@@ -2177,6 +2177,7 @@
 	treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dialog->model));
 	dialog->treeview = treeview;
 	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
+	g_object_unref(G_OBJECT(dialog->model));
 
 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
 	gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
--- a/pidgin/gtkblist.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/pidgin/gtkblist.c	Wed Jan 30 10:53:22 2008 +0000
@@ -937,7 +937,6 @@
 pidgin_blist_joinchat_show(void)
 {
 	GtkWidget *hbox, *vbox;
-	GtkWidget *rowbox;
 	GtkWidget *label;
 	PidginBuddyList *gtkblist;
 	GtkWidget *img = NULL;
@@ -981,7 +980,6 @@
 	data->account_menu = pidgin_account_option_menu_new(NULL, FALSE,
 			G_CALLBACK(joinchat_select_account_cb),
 			chat_account_filter_func, data);
-	gtk_box_pack_start(GTK_BOX(rowbox), data->account_menu, TRUE, TRUE, 0);
 
 	pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Account:"), data->sg, data->account_menu, TRUE, NULL);
 
@@ -4145,12 +4143,11 @@
 static void pidgin_blist_new_list(PurpleBuddyList *blist)
 {
 	PidginBuddyList *gtkblist;
-	PidginBuddyListPrivate *priv;
 
 	gtkblist = g_new0(PidginBuddyList, 1);
 	gtkblist->connection_errors = g_hash_table_new_full(g_direct_hash,
 												g_direct_equal, NULL, g_free);
-	gtkblist->priv = priv = g_new0(PidginBuddyListPrivate, 1);
+	gtkblist->priv = g_new0(PidginBuddyListPrivate, 1);
 
 	blist->ui_data = gtkblist;
 }
@@ -6071,6 +6068,7 @@
 	gtkblist->timeout = 0;
 	gtkblist->drag_timeout = 0;
 	gtkblist->window = gtkblist->vbox = gtkblist->treeview = NULL;
+	g_object_unref(G_OBJECT(gtkblist->treemodel));
 	gtkblist->treemodel = NULL;
 	g_object_unref(G_OBJECT(gtkblist->ift));
 	g_object_unref(G_OBJECT(gtkblist->empty_avatar));
--- a/pidgin/gtkcertmgr.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/pidgin/gtkcertmgr.c	Wed Jan 30 10:53:22 2008 +0000
@@ -373,7 +373,7 @@
 		
 		purple_request_yes_no(tpm_dat, _("Confirm certificate delete"),
 				      primary, NULL, /* Can this be NULL? */
-				      2, /* NO is default action */
+				      1, /* NO is default action */
 				      NULL, NULL, NULL,
 				      id, /* id ownership passed to callback */
 				      tls_peers_mgmt_delete_confirm_cb,
@@ -422,6 +422,7 @@
 	
 	tpm_dat->listview = listview =
 		GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)));
+	g_object_unref(G_OBJECT(store));
 	
 	{
 		GtkCellRenderer *renderer;
--- a/pidgin/gtkconv.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/pidgin/gtkconv.c	Wed Jan 30 10:53:22 2008 +0000
@@ -173,6 +173,7 @@
 
 static void pidgin_conv_set_position_size(PidginWindow *win, int x, int y,
 		int width, int height);
+static gboolean pidgin_conv_xy_to_right_infopane(PidginWindow *win, int x, int y);
 
 static GdkColor *get_nick_color(PidginConversation *gtkconv, const char *name) {
 	static GdkColor col;
@@ -494,6 +495,13 @@
 
 		cmdline = cmd + strlen(prefix);
 
+		if (strcmp(cmdline, "xyzzy") == 0) {
+			purple_conversation_write(conv, "", "Nothing happens",
+					PURPLE_MESSAGE_NO_LOG, time(NULL));
+			g_free(cmd);
+			return TRUE;
+		}
+
 		gtk_text_iter_forward_chars(&start, g_utf8_strlen(prefix, -1));
 		gtk_text_buffer_get_end_iter(GTK_IMHTML(gtkconv->entry)->text_buffer, &end);
 		markup = gtk_imhtml_get_markup_range(GTK_IMHTML(gtkconv->entry), &start, &end);
@@ -3489,9 +3497,14 @@
 		gtk_text_buffer_delete_mark(buffer, stmark);
 		gtk_text_buffer_delete_mark(buffer, enmark);
 		gtk_text_buffer_delete(buffer, &start, &end);
-	} else if (message && *message == '\n' && !*(message + 1))
+	} else if (message && *message == '\n' && message[1] == ' ' && message[2] == '\0')
 		message = NULL;
 
+#ifdef RESERVE_LINE
+	if (!message)
+		message = "\n ";   /* The blank space is required to avoid a GTK+/Pango bug */
+#endif
+
 	if (message) {
 		GtkTextIter iter;
 		gtk_text_buffer_get_end_iter(buffer, &iter);
@@ -3519,7 +3532,11 @@
 		return;
 
 	if (purple_conv_im_get_typing_state(im) == PURPLE_NOT_TYPING) {
-		update_typing_message(gtkconv, "\n");
+#ifdef RESERVE_LINE
+		update_typing_message(gtkconv, NULL);
+#else
+		update_typing_message(gtkconv, "\n ");
+#endif
 		return;
 	}
 
@@ -5070,7 +5087,7 @@
 	gtk_text_buffer_create_tag(GTK_IMHTML(gtkconv->imhtml)->text_buffer, "TYPING-NOTIFICATION",
 			"foreground", "#888888",
 			"justification", GTK_JUSTIFY_LEFT,  /* XXX: RTL'ify */
-			"weight", PANGO_WEIGHT_BOLD,
+			"weight", PANGO_WEIGHT_LIGHT,
 			"scale", PANGO_SCALE_SMALL,
 			NULL);
 
@@ -6988,6 +7005,18 @@
 		gray_stuff_out(PIDGIN_CONVERSATION(conv));
 }
 
+static gboolean
+pidgin_conv_xy_to_right_infopane(PidginWindow *win, int x, int y)
+{
+	gint pane_x, pane_y, x_rel;
+	PidginConversation *gtkconv;
+
+	gdk_window_get_origin(win->notebook->window, &pane_x, &pane_y);
+	x_rel = x - pane_x;
+	gtkconv = pidgin_conv_window_get_active_gtkconv(win);
+	return (x_rel > gtkconv->infopane->allocation.x + gtkconv->infopane->allocation.width / 2);
+}
+
 int
 pidgin_conv_get_tab_at_xy(PidginWindow *win, int x, int y, gboolean *to_right)
 {
@@ -7023,7 +7052,7 @@
 		tab = gtk_notebook_get_tab_label(GTK_NOTEBOOK(notebook), page);
 
 		/* Make sure the tab is not hidden beyond an arrow */
-		if (!GTK_WIDGET_DRAWABLE(tab))
+		if (!GTK_WIDGET_DRAWABLE(tab) && gtk_notebook_get_show_tabs(notebook))
 			continue;
 
 		if (horiz) {
@@ -8304,7 +8333,6 @@
 		GtkWidget *tab;
 		gint page_num;
 		gboolean horiz_tabs = FALSE;
-		PidginConversation *gtkconv;
 		gboolean to_right = FALSE;
 
 		/* Get the window that the cursor is over. */
@@ -8318,20 +8346,27 @@
 
 		dest_notebook = GTK_NOTEBOOK(dest_win->notebook);
 
-		page_num = pidgin_conv_get_tab_at_xy(dest_win,
-		                                      e->x_root, e->y_root, &to_right);
-		to_right = to_right && (win != dest_win);
+		if (gtk_notebook_get_show_tabs(dest_notebook)) {
+			page_num = pidgin_conv_get_tab_at_xy(dest_win,
+			                                      e->x_root, e->y_root, &to_right);
+			to_right = to_right && (win != dest_win);
+			tab = pidgin_conv_window_get_gtkconv_at_index(dest_win, page_num)->tabby;
+		} else {
+			page_num = 0;
+			to_right = pidgin_conv_xy_to_right_infopane(dest_win, e->x_root, e->y_root);
+			tab = pidgin_conv_window_get_gtkconv_at_index(dest_win, page_num)->infopane;
+		}
 
 		if (gtk_notebook_get_tab_pos(dest_notebook) == GTK_POS_TOP ||
 				gtk_notebook_get_tab_pos(dest_notebook) == GTK_POS_BOTTOM) {
 			horiz_tabs = TRUE;
 		}
 
-		gtkconv = pidgin_conv_window_get_gtkconv_at_index(dest_win, page_num);
-		tab = gtkconv->tabby;
-		if (gtk_notebook_get_show_tabs(dest_notebook) == FALSE) {
-				dnd_hints_show_relative(HINT_ARROW_DOWN, gtkconv->infopane, HINT_POSITION_CENTER, HINT_POSITION_TOP);
-				dnd_hints_show_relative(HINT_ARROW_UP, gtkconv->infopane, HINT_POSITION_CENTER, HINT_POSITION_BOTTOM);
+		if (gtk_notebook_get_show_tabs(dest_notebook) == FALSE && win == dest_win)
+		{
+			/* dragging a tab from a single-tabbed window over its own window */
+			dnd_hints_hide_all();
+			return TRUE;
 		} else if (horiz_tabs) {
 			if (((gpointer)win == (gpointer)dest_win && win->drag_tab < page_num) || to_right) {
 				dnd_hints_show_relative(HINT_ARROW_DOWN, tab, HINT_POSITION_RIGHT, HINT_POSITION_TOP);
@@ -8528,6 +8563,7 @@
 notebook_release_cb(GtkWidget *widget, GdkEventButton *e, PidginWindow *win)
 {
 	PidginWindow *dest_win;
+	GtkNotebook *dest_notebook;
 	PurpleConversation *conv;
 	PidginConversation *gtkconv;
 	gint dest_page_num = 0;
@@ -8603,9 +8639,16 @@
 	                 "conversation-dragging", win, dest_win);
 
 	/* Get the destination page number. */
-	if (!new_window)
-		dest_page_num = pidgin_conv_get_tab_at_xy(dest_win,
-		                                           e->x_root, e->y_root, &to_right);
+	if (!new_window) {
+		dest_notebook = GTK_NOTEBOOK(dest_win->notebook);
+		if (gtk_notebook_get_show_tabs(dest_notebook)) {
+			dest_page_num = pidgin_conv_get_tab_at_xy(dest_win,
+			                                           e->x_root, e->y_root, &to_right);
+		} else {
+			dest_page_num = 0;
+			to_right = pidgin_conv_xy_to_right_infopane(dest_win, e->x_root, e->y_root);
+		}
+	}
 
 	gtkconv = pidgin_conv_window_get_gtkconv_at_index(win, win->drag_tab);
 
--- a/pidgin/gtkimhtml.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/pidgin/gtkimhtml.c	Wed Jan 30 10:53:22 2008 +0000
@@ -1463,6 +1463,9 @@
 	gtk_text_buffer_create_tag(imhtml->text_buffer, "SUP", "rise", 5000, NULL);
 	gtk_text_buffer_create_tag(imhtml->text_buffer, "PRE", "family", "Monospace", NULL);
 	gtk_text_buffer_create_tag(imhtml->text_buffer, "search", "background", "#22ff00", "weight", "bold", NULL);
+#if GTK_CHECK_VERSION(2,10,10)
+	gtk_text_buffer_create_tag(imhtml->text_buffer, "comment", "invisible", FALSE, NULL);
+#endif
 
 	/* When hovering over a link, we show the hand cursor--elsewhere we show the plain ol' pointer cursor */
 	imhtml->hand_cursor = gdk_cursor_new (GDK_HAND2);
@@ -2989,10 +2992,15 @@
 
 					gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
 
+#if GTK_CHECK_VERSION(2,10,10)
+					wpos = g_snprintf (ws, len, "%s", tag);
+					gtk_text_buffer_insert_with_tags_by_name(imhtml->text_buffer, iter, ws, wpos, "comment", NULL);
+#else
 					if (imhtml->show_comments && !(options & GTK_IMHTML_NO_COMMENTS)) {
 						wpos = g_snprintf (ws, len, "%s", tag);
 						gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
 					}
+#endif
 					ws[0] = '\0'; wpos = 0;
 
 					/* NEW_BIT (NEW_COMMENT_BIT); */
@@ -3138,6 +3146,12 @@
 void       gtk_imhtml_show_comments    (GtkIMHtml        *imhtml,
 					gboolean          show)
 {
+#if GTK_CHECK_VERSION(2,10,10)
+	GtkTextTag *tag;
+	tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), "comment");
+	if (tag)
+		g_object_set(G_OBJECT(tag), "invisible", !show, NULL);
+#endif
 	imhtml->show_comments = show;
 }
 
--- a/pidgin/gtkimhtmltoolbar.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Wed Jan 30 10:53:22 2008 +0000
@@ -1312,6 +1312,7 @@
 
 	gtk_box_pack_start(GTK_BOX(hbox), box, FALSE, FALSE, 0);
 	g_object_set_data(G_OBJECT(hbox), "lean-view", box);
+	gtk_widget_show(box);
 
 	purple_prefs_connect_callback(toolbar, PIDGIN_PREFS_ROOT "/conversations/toolbar/wide",
 			imhtmltoolbar_view_pref_changed, toolbar);
--- a/pidgin/gtklog.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/pidgin/gtklog.c	Wed Jan 30 10:53:22 2008 +0000
@@ -139,7 +139,7 @@
 	{
 		/* Searching for the same term acts as "Find Next" */
 		gtk_imhtml_search_find(GTK_IMHTML(lv->imhtml), lv->search);
-		return;	
+		return;
 	}
 
 	pidgin_set_cursor(lv->window, GDK_WATCH);
@@ -321,7 +321,7 @@
 	data2[0] = lv->treestore;
 	data2[1] = data[3]; /* iter */
 	data2[2] = log;
-	purple_request_action(lv, NULL, "Delete Log?", tmp, 0, 
+	purple_request_action(lv, NULL, "Delete Log?", tmp, 0,
 						NULL, NULL, NULL,
 						data2, 2,
 						_("Delete"), delete_log_cb,
@@ -556,8 +556,13 @@
 				if (!purple_prefs_get_bool("/purple/logging/log_chats"))
 					log_preferences = _("Chats will only be logged if the \"Log all chats\" preference is enabled.");
 			}
+			g_free(ht->screenname);
+			g_free(ht);
 		}
 
+		if(icon != NULL)
+			gtk_widget_destroy(icon);
+
 		purple_notify_info(NULL, title, _("No logs were found"), log_preferences);
 		return NULL;
 	}
@@ -614,6 +619,7 @@
 	gtk_paned_add1(GTK_PANED(pane), sw);
 	lv->treestore = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
 	lv->treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (lv->treestore));
+	g_object_unref(G_OBJECT(lv->treestore));
 	rend = gtk_cell_renderer_text_new();
 	col = gtk_tree_view_column_new_with_attributes ("time", rend, "markup", 0, NULL);
 	gtk_tree_view_append_column (GTK_TREE_VIEW(lv->treeview), col);
@@ -730,18 +736,19 @@
 }
 
 void pidgin_log_show_contact(PurpleContact *contact) {
-	struct log_viewer_hash_t *ht = g_new0(struct log_viewer_hash_t, 1);
+	struct log_viewer_hash_t *ht;
 	PurpleBlistNode *child;
 	PidginLogViewer *lv = NULL;
 	GList *logs = NULL;
 	GdkPixbuf *pixbuf;
-	GtkWidget *image = gtk_image_new();
+	GtkWidget *image;
 	const char *name = NULL;
 	char *title;
 	int total_log_size = 0;
 
 	g_return_if_fail(contact != NULL);
 
+	ht = g_new0(struct log_viewer_hash_t, 1);
 	ht->type = PURPLE_LOG_IM;
 	ht->contact = contact;
 
@@ -763,9 +770,16 @@
 	}
 	logs = g_list_sort(logs, purple_log_compare);
 
+	image = gtk_image_new();
 	pixbuf = gtk_widget_render_icon(image, PIDGIN_STOCK_STATUS_PERSON,
 					gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL), "GtkWindow");
-	gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf);
+	if (pixbuf) {
+		gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf);
+		g_object_unref(pixbuf);
+	} else {
+		gtk_widget_destroy(image);
+		image = NULL;
+	}
 
 	if (contact->alias != NULL)
 		name = contact->alias;
--- a/pidgin/gtkmain.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/pidgin/gtkmain.c	Wed Jan 30 10:53:22 2008 +0000
@@ -510,6 +510,7 @@
 		{"session",  required_argument, NULL, 's'},
 		{"version",  no_argument,       NULL, 'v'},
 		{"display",  required_argument, NULL, 'D'},
+		{"sync",     no_argument,       NULL, 'S'},
 		{0, 0, 0, 0}
 	};
 
@@ -519,7 +520,7 @@
 	debug_enabled = FALSE;
 #endif
 
-	/* This is the first Glib function call. Make sure to initialize GThread bfeore then */
+	/* Initialize GThread before calling any Glib or GTK+ functions. */
 	g_thread_init(NULL);
 
 #ifdef ENABLE_NLS
@@ -656,6 +657,7 @@
 			opt_si = FALSE;
 			break;
 		case 'D':   /* --display */
+		case 'S':   /* --sync */
 			/* handled by gtk_init_check below */
 			break;
 		case '?':	/* show terse help */
--- a/pidgin/gtknotify.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/pidgin/gtknotify.c	Wed Jan 30 10:53:22 2008 +0000
@@ -379,6 +379,7 @@
 		mail_dialog->treemodel = gtk_tree_store_new(COLUMNS_PIDGIN_MAIL,
 						GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_POINTER);
 		mail_dialog->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(mail_dialog->treemodel));
+		g_object_unref(G_OBJECT(mail_dialog->treemodel));
 		gtk_tree_view_set_search_column(GTK_TREE_VIEW(mail_dialog->treeview), PIDGIN_MAIL_TEXT);
 		gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(mail_dialog->treeview),
 			             pidgin_tree_view_search_equal_func, NULL, NULL);
@@ -818,6 +819,7 @@
 
 	/* Setup the treeview */
 	treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
+	g_object_unref(G_OBJECT(model));
 	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
 	gtk_widget_set_size_request(treeview, 500, 400);
 	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)),
@@ -1056,7 +1058,12 @@
 	/* if they are running gnome, use the gnome web browser */
 	if (purple_running_gnome() == TRUE)
 	{
-		command = g_strdup_printf("gnome-open %s", escaped);
+		char *tmp = g_find_program_in_path("xdg-open");
+		if (tmp == NULL)
+			command = g_strdup_printf("gnome-open %s", escaped);
+		else
+			command = g_strdup_printf("xdg-open %s", escaped);
+		g_free(tmp);
 	}
 	else if (purple_running_osx() == TRUE)
 	{
@@ -1072,6 +1079,10 @@
 		else
 			command = g_strdup_printf("%s %s", web_browser, escaped);
 	}
+	else if (!strcmp(web_browser, "xdg-open"))
+	{
+		command = g_strdup_printf("xdg-open %s", escaped);
+	}
 	else if (!strcmp(web_browser, "gnome-open"))
 	{
 		command = g_strdup_printf("gnome-open %s", escaped);
--- a/pidgin/gtkpounce.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/pidgin/gtkpounce.c	Wed Jan 30 10:53:22 2008 +0000
@@ -1237,6 +1237,7 @@
 
 	/* Create the treeview */
 	treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dialog->model));
+	g_object_unref(G_OBJECT(dialog->model));
 	dialog->treeview = treeview;
 	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
 
--- a/pidgin/gtkprefs.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/pidgin/gtkprefs.c	Wed Jan 30 10:53:22 2008 +0000
@@ -478,12 +478,20 @@
 {
 	FILE *f;
 	gchar *path;
+	size_t wc;
 
 	if ((error_message != NULL) || (len == 0))
 		return;
 
 	f = purple_mkstemp(&path, TRUE);
-	fwrite(themedata, len, 1, f);
+	wc = fwrite(themedata, len, 1, f);
+	if (wc != 1) {
+		purple_debug_warning("theme_got_url", "Unable to write theme data.\n");
+		fclose(f);
+		g_unlink(path);
+		g_free(path);
+		return;
+	}
 	fclose(f);
 
 	theme_install_theme(path, user_data);
@@ -1364,6 +1372,7 @@
 		{N_("Netscape"), "netscape"},
 		{N_("Mozilla"), "mozilla"},
 		{N_("Konqueror"), "kfmclient"},
+		{N_("Desktop Default"), "xdg-open"},
 		{N_("GNOME Default"), "gnome-open"},
 		{N_("Galeon"), "galeon"},
 		{N_("Firefox"), "firefox"},
@@ -1386,6 +1395,14 @@
 			browsers = g_list_prepend(browsers, (gpointer)_(possible_browsers[i].name));
 			if(browser_setting && !strcmp(possible_browsers[i].command, browser_setting))
 				browser_setting = NULL;
+			/* If xdg-open is valid, prefer it over gnome-open and skip forward */
+			if(!strcmp(possible_browsers[i].command, "xdg-open")) {
+				if (browser_setting && !strcmp("gnome-open", browser_setting)) {
+					purple_prefs_set_string(PIDGIN_PREFS_ROOT "/browsers/browser", possible_browsers[i].command);
+					browser_setting = NULL;
+				}
+				i++;
+			}
 		}
 	}
 
@@ -1460,15 +1477,15 @@
 	}
 
 	entry = gtk_entry_new();
-	if (strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/browsers/browser"), "custom"))
-		gtk_widget_set_sensitive(hbox, FALSE);
-	purple_prefs_connect_callback(prefs, PIDGIN_PREFS_ROOT "/browsers/browser",
-								browser_changed2_cb, hbox);
 	gtk_entry_set_text(GTK_ENTRY(entry),
 					   purple_prefs_get_path(PIDGIN_PREFS_ROOT "/browsers/command"));
 	g_signal_connect(G_OBJECT(entry), "focus-out-event",
 					 G_CALLBACK(manual_browser_set), NULL);
-	pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Manual:\n(%s for URL)"), sg, entry, TRUE, NULL);
+	hbox = pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Manual:\n(%s for URL)"), sg, entry, TRUE, NULL);
+	if (strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/browsers/browser"), "custom"))
+		gtk_widget_set_sensitive(hbox, FALSE);
+	purple_prefs_connect_callback(prefs, PIDGIN_PREFS_ROOT "/browsers/browser",
+			browser_changed2_cb, hbox);
 
 	gtk_widget_show_all(ret);
 	g_object_unref(sg);
@@ -1721,9 +1738,6 @@
 	int j;
 	const char *file;
 	char *pref;
-#if !defined _WIN32 || defined USE_GSTREAMER
-	GtkWidget *label;
-#endif
 #ifndef _WIN32
 	GtkWidget *dd;
 	GtkWidget *entry;
--- a/pidgin/gtkprivacy.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/pidgin/gtkprivacy.c	Wed Jan 30 10:53:22 2008 +0000
@@ -474,6 +474,8 @@
 	if (privacy_dialog == NULL)
 		return;
 
+	g_object_unref(G_OBJECT(privacy_dialog->allow_store));
+	g_object_unref(G_OBJECT(privacy_dialog->block_store));
 	g_free(privacy_dialog);
 	privacy_dialog = NULL;
 }
--- a/pidgin/gtkrequest.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/pidgin/gtkrequest.c	Wed Jan 30 10:53:22 2008 +0000
@@ -663,7 +663,11 @@
 		gtk_widget_grab_focus(img);
 		gtk_widget_grab_default(img);
 	} else
-		gtk_dialog_set_default_response(GTK_DIALOG(dialog), default_action);
+		/*
+		 * Need to invert the default_action number because the
+		 * buttons are added to the dialog in reverse order.
+		 */
+		gtk_dialog_set_default_response(GTK_DIALOG(dialog), action_count - 1 - default_action);
 
 	/* Show everything. */
 	pidgin_auto_parent_window(dialog);
@@ -1007,6 +1011,7 @@
 
 	/* Create the tree view */
 	treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
+	g_object_unref(G_OBJECT(store));
 	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE);
 
 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
--- a/pidgin/gtksavedstatuses.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/pidgin/gtksavedstatuses.c	Wed Jan 30 10:53:22 2008 +0000
@@ -669,6 +669,7 @@
 	purple_request_close_with_handle(status_window);
 	purple_notify_close_with_handle(status_window);
 	purple_signals_disconnect_by_handle(status_window);
+	g_object_unref(G_OBJECT(status_window->model));
 	g_free(status_window);
 	status_window = NULL;
 }
@@ -723,6 +724,7 @@
 
 	status_editor_remove_dialog(dialog);
 	g_free(dialog->original_title);
+	g_object_unref(G_OBJECT(dialog->model));
 	g_free(dialog);
 
 	return FALSE;
--- a/pidgin/gtkstatusbox.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/pidgin/gtkstatusbox.c	Wed Jan 30 10:53:22 2008 +0000
@@ -320,12 +320,20 @@
 {
 	FILE *f;
 	gchar *path;
+	size_t wc;
 
 	if ((error_message != NULL) || (len == 0))
 		return;
 
 	f = purple_mkstemp(&path, TRUE);
-	fwrite(themedata, len, 1, f);
+	wc = fwrite(themedata, len, 1, f);
+	if (wc != 1) {
+		purple_debug_warning("theme_got_url", "Unable to write theme data.\n");
+		fclose(f);
+		g_unlink(path);
+		g_free(path);
+		return;
+	}
 	fclose(f);
 
 	icon_choose_cb(path, user_data);
@@ -503,20 +511,28 @@
 pidgin_status_box_finalize(GObject *obj)
 {
 	PidginStatusBox *statusbox = PIDGIN_STATUS_BOX(obj);
+	int i;
 
 	purple_signals_disconnect_by_handle(statusbox);
 	purple_prefs_disconnect_by_handle(statusbox);
 
-	gdk_cursor_unref(statusbox->hand_cursor);
-	gdk_cursor_unref(statusbox->arrow_cursor);
+	destroy_icon_box(statusbox);
+
+	if (statusbox->active_row)
+		gtk_tree_row_reference_free(statusbox->active_row);
 
-	purple_imgstore_unref(statusbox->buddy_icon_img);
-	g_object_unref(G_OBJECT(statusbox->buddy_icon));
-	g_object_unref(G_OBJECT(statusbox->buddy_icon_hover));
+	for (i = 0; i < G_N_ELEMENTS(statusbox->connecting_pixbufs); i++) {
+		if (statusbox->connecting_pixbufs[i] != NULL)
+			gdk_pixbuf_unref(statusbox->connecting_pixbufs[i]);
+	}
 
-	if (statusbox->buddy_icon_sel)
-		gtk_widget_destroy(statusbox->buddy_icon_sel);
+	for (i = 0; i < G_N_ELEMENTS(statusbox->typing_pixbufs); i++) {
+		if (statusbox->typing_pixbufs[i] != NULL)
+			gdk_pixbuf_unref(statusbox->typing_pixbufs[i]);
+	}
 
+	g_object_unref(G_OBJECT(statusbox->store));
+	g_object_unref(G_OBJECT(statusbox->dropdown_store));
 	G_OBJECT_CLASS(parent_class)->finalize(obj);
 }
 
@@ -1164,18 +1180,15 @@
 cache_pixbufs(PidginStatusBox *status_box)
 {
 	GtkIconSize icon_size;
+	int i;
 
 	g_object_set(G_OBJECT(status_box->icon_rend), "xpad", 3, NULL);
 	icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL);
 
-	if (status_box->connecting_pixbufs[0] != NULL)
-		gdk_pixbuf_unref(status_box->connecting_pixbufs[0]);
-	if (status_box->connecting_pixbufs[1] != NULL)
-		gdk_pixbuf_unref(status_box->connecting_pixbufs[1]);
-	if (status_box->connecting_pixbufs[2] != NULL)
-		gdk_pixbuf_unref(status_box->connecting_pixbufs[2]);
-	if (status_box->connecting_pixbufs[3] != NULL)
-		gdk_pixbuf_unref(status_box->connecting_pixbufs[3]);
+	for (i = 0; i < G_N_ELEMENTS(status_box->connecting_pixbufs); i++) {
+		if (status_box->connecting_pixbufs[i] != NULL)
+			gdk_pixbuf_unref(status_box->connecting_pixbufs[i]);
+	}
 
 	status_box->connecting_index = 0;
 	status_box->connecting_pixbufs[0] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_CONNECT0,
@@ -1197,14 +1210,10 @@
 	status_box->connecting_pixbufs[8] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_CONNECT8,
 								     icon_size, "PidginStatusBox");
 
-	if (status_box->typing_pixbufs[0] != NULL)
-		gdk_pixbuf_unref(status_box->typing_pixbufs[0]);
-	if (status_box->typing_pixbufs[1] != NULL)
-		gdk_pixbuf_unref(status_box->typing_pixbufs[1]);
-	if (status_box->typing_pixbufs[2] != NULL)
-		gdk_pixbuf_unref(status_box->typing_pixbufs[2]);
-	if (status_box->typing_pixbufs[3] != NULL)
-		gdk_pixbuf_unref(status_box->typing_pixbufs[3]);
+	for (i = 0; i < G_N_ELEMENTS(status_box->typing_pixbufs); i++) {
+		if (status_box->typing_pixbufs[i] != NULL)
+			gdk_pixbuf_unref(status_box->typing_pixbufs[i]);
+	}
 
 	status_box->typing_index = 0;
 	status_box->typing_pixbufs[0] =  gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_TYPING0,
--- a/pidgin/gtkutils.c	Thu Jan 10 05:32:41 2008 +0000
+++ b/pidgin/gtkutils.c	Wed Jan 30 10:53:22 2008 +0000
@@ -1595,7 +1595,7 @@
 			else if (!(im || ft))
 				purple_request_yes_no(NULL, NULL, _("You have dragged an image"),
 							_("Would you like to set it as the buddy icon for this user?"),
-							0,
+							PURPLE_DEFAULT_ACTION_NONE,
 							account, who, NULL,
 							data, (GCallback)dnd_set_icon_ok_cb, (GCallback)dnd_set_icon_cancel_cb);
 			else
--- a/pidgin/gtkutils.h	Thu Jan 10 05:32:41 2008 +0000
+++ b/pidgin/gtkutils.h	Wed Jan 30 10:53:22 2008 +0000
@@ -798,11 +798,11 @@
  * Add a labelled widget to a GtkVBox
  *
  * @param vbox         The GtkVBox to add the widget to.
- * @param widget_label The label to give the widget.
- * @param sg           The GtkSizeGroup to add the label to.
- * @param widget       The GtkWidget to add
+ * @param widget_label The label to give the widget, can be @c NULL.
+ * @param sg           The GtkSizeGroup to add the label to, can be @c NULL.
+ * @param widget       The GtkWidget to add.
  * @param expand       Whether to expand the widget horizontally.
- * @param p_label      Place to store a pointer to the GtkLabel, or NULL if you don't care.
+ * @param p_label      Place to store a pointer to the GtkLabel, or @c NULL if you don't care.
  *
  * @return  A GtkHBox already added to the GtkVBox containing the GtkLabel and the GtkWidget.
  * @since 2.4.0
--- a/pidgin/win32/nsis/pidgin-installer.nsi	Thu Jan 10 05:32:41 2008 +0000
+++ b/pidgin/win32/nsis/pidgin-installer.nsi	Wed Jan 30 10:53:22 2008 +0000
@@ -1167,6 +1167,10 @@
     MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION $(PIDGIN_IS_RUNNING) /SD IDCANCEL IDRETRY retry_runcheck
     Abort
 
+  ; Close the Handle (If we don't do this, the uninstaller called from within will fail)
+  ; This is not optimal because there is a (small) window of time when a new process could start
+  System::Call 'kernel32::CloseHandle(i $R1) i .R1'
+
   Pop $R1
   Pop $R0
 FunctionEnd
--- a/po/POTFILES.in	Thu Jan 10 05:32:41 2008 +0000
+++ b/po/POTFILES.in	Wed Jan 30 10:53:22 2008 +0000
@@ -33,6 +33,7 @@
 finch/plugins/gntclipboard.c
 finch/plugins/gntgf.c
 finch/plugins/gnthistory.c
+finch/plugins/grouping.c
 finch/plugins/lastlog.c
 libpurple/account.c
 libpurple/blist.c
@@ -72,6 +73,7 @@
 libpurple/protocols/bonjour/bonjour.c
 libpurple/protocols/bonjour/bonjour.h
 libpurple/protocols/bonjour/jabber.c
+libpurple/protocols/bonjour/mdns_win32.c
 libpurple/protocols/gg/gg.c
 libpurple/protocols/irc/cmds.c
 libpurple/protocols/irc/dcc_send.c
@@ -213,6 +215,7 @@
 pidgin/pidgin.h
 pidgin/pidgincombobox.c
 pidgin/pidginstock.c
+pidgin/pidgintooltip.c
 pidgin/pixmaps/emotes/default/24/default.theme.in
 pidgin/pixmaps/emotes/none/none.theme.in
 pidgin/plugins/cap/cap.c
--- a/po/de.po	Thu Jan 10 05:32:41 2008 +0000
+++ b/po/de.po	Wed Jan 30 10:53:22 2008 +0000
@@ -11,8 +11,8 @@
 msgstr ""
 "Project-Id-Version: de\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-01-08 11:16+0100\n"
-"PO-Revision-Date: 2008-01-08 11:15+0100\n"
+"POT-Creation-Date: 2008-01-23 10:20+0100\n"
+"PO-Revision-Date: 2008-01-23 10:19+0100\n"
 "Last-Translator: Jochen Kemnade <jochenkemnade@web.de>\n"
 "Language-Team: Deutsch <de@li.org>\n"
 "MIME-Version: 1.0\n"
@@ -155,6 +155,29 @@
 msgid "Deny"
 msgstr "Ablehnen"
 
+#, c-format
+msgid ""
+"Online: %d\n"
+"Total: %d"
+msgstr ""
+"Online: %d\n"
+"Gesamt: %d"
+
+#, c-format
+msgid "Account: %s (%s)"
+msgstr "Konto: %s (%s)"
+
+#, c-format
+msgid ""
+"\n"
+"Last Seen: %s ago"
+msgstr ""
+"\n"
+"Zuletzt gesehen: vor %s"
+
+msgid "Default"
+msgstr "Standard"
+
 msgid "You must provide a screename for the buddy."
 msgstr "Sie müssen einen Benutzernamen für den Buddy angeben."
 
@@ -303,26 +326,6 @@
 msgid "On Mobile"
 msgstr "Am Handy"
 
-#, c-format
-msgid ""
-"Online: %d\n"
-"Total: %d"
-msgstr ""
-"Online: %d\n"
-"Gesamt: %d"
-
-#, c-format
-msgid "Account: %s (%s)"
-msgstr "Konto: %s (%s)"
-
-#, c-format
-msgid ""
-"\n"
-"Last Seen: %s ago"
-msgstr ""
-"\n"
-"Zuletzt gesehen: vor %s"
-
 msgid "New..."
 msgstr "Neu..."
 
@@ -389,6 +392,15 @@
 msgid "By Log Size"
 msgstr "Nach Größe der Logs"
 
+msgid "Buddy"
+msgstr "Buddy"
+
+msgid "Chat"
+msgstr "Chat"
+
+msgid "Grouping"
+msgstr "Gruppierung"
+
 msgid "Certificate Import"
 msgstr "Zertifikat-Import"
 
@@ -1321,6 +1333,27 @@
 "Wenn eine neue Unterhaltung eröffnet wird, fügt dieses Plugin die letzte "
 "Unterhaltung in die aktuelle Unterhaltung ein."
 
+msgid "Online"
+msgstr "Online"
+
+msgid "Offline"
+msgstr "Offline"
+
+msgid "Online Buddies"
+msgstr "Online-Buddys"
+
+msgid "Offline Buddies"
+msgstr "Offline-Buddys"
+
+msgid "Online/Offline"
+msgstr "Online/Offline"
+
+msgid "No Grouping"
+msgstr "Keine Gruppierung"
+
+msgid "Provides alternate buddylist grouping options."
+msgstr "Bietet alternative Einstellungen für die Kontaktlisten-Gruppierung."
+
 msgid "Lastlog"
 msgstr "Verlauf"
 
@@ -2688,6 +2721,9 @@
 msgid "Could not listen on socket"
 msgstr "Kann nicht an der Socket hören"
 
+msgid "Error communicating with local mDNSResponder."
+msgstr "Fehler bei der Kommunikation mit lokalem mDNSResponder."
+
 msgid "Invalid proxy settings"
 msgstr "Falsche Proxy-Einstellungen"
 
@@ -2819,9 +2855,6 @@
 msgid "Add to chat..."
 msgstr "Zum Chat hinzufügen..."
 
-msgid "Offline"
-msgstr "Offline"
-
 msgid "Available"
 msgstr "Verfügbar"
 
@@ -4237,6 +4270,8 @@
 msgid "Unable to buzz, because the user %s does not support it."
 msgstr "Kann nicht anklopfen, da der Benutzer %s dies nicht unterstützt."
 
+#. Yahoo only supports one attention command: the 'buzz'.
+#. This is index number YAHOO_BUZZ.
 msgid "Buzz"
 msgstr "Anklopfen"
 
@@ -5971,9 +6006,6 @@
 msgid "AIM Direct IM"
 msgstr "AIM direkte Nachricht"
 
-msgid "Chat"
-msgstr "Chat"
-
 msgid "Get File"
 msgstr "Datei abrufen"
 
@@ -6043,9 +6075,6 @@
 msgid "Invisible"
 msgstr "Unsichtbar"
 
-msgid "Online"
-msgstr "Online"
-
 msgid "IP Address"
 msgstr "IP-Adresse"
 
@@ -9151,6 +9180,10 @@
 msgid "Unable to establish file descriptor."
 msgstr "Konnte Dateibeschreibung nicht erstellen."
 
+#, c-format
+msgid "%s is trying to send you a group of %d files.\n"
+msgstr "%s versucht, Ihnen eine Gruppe von %d Dateien zu senden.\n"
+
 msgid "Write Error"
 msgstr "Schreibfehler"
 
@@ -10072,9 +10105,6 @@
 msgid "Total Buddies"
 msgstr "Buddy-Anzahl"
 
-msgid "Online Buddies"
-msgstr "Online-Buddys"
-
 #, c-format
 msgid "Idle %dd %dh %02dm"
 msgstr "Untätig %dd %dh %02dm"
@@ -11682,9 +11712,6 @@
 msgid "Pounce Target"
 msgstr "Alarm-Ziel"
 
-msgid "Default"
-msgstr "Standard"
-
 msgid "Smiley theme failed to unpack."
 msgstr "Smiley-Thema konnte nicht entpackt werden."
 
@@ -12295,6 +12322,9 @@
 msgid "_Open Mail"
 msgstr "Mail ö_ffnen"
 
+msgid "Pidgin Tooltip"
+msgstr "Pidgin-Tooltip"
+
 msgid "Pidgin smileys"
 msgstr "Pidgin-Smileys"
 
--- a/po/en_GB.po	Thu Jan 10 05:32:41 2008 +0000
+++ b/po/en_GB.po	Wed Jan 30 10:53:22 2008 +0000
@@ -3315,7 +3315,7 @@
 
 #: ../libpurple/plugins/psychic.c:72
 msgid "You feel a disturbance in the force..."
-msgstr "You have a disturbance in the force..."
+msgstr "You feel a disturbance in the force..."
 
 #: ../libpurple/plugins/psychic.c:91
 msgid "Only enable for users on the buddy list"