changeset 26807:8d47c0d594dc

merge of '02982091b25d51cf15064b67811f0ff347e3ec48' and 'acf47162ca3864daf33d64a93e65604bf88ec07e'
author Paul Aurich <paul@darkrain42.org>
date Sun, 03 May 2009 20:18:23 +0000
parents 1dfa009f6db5 (diff) 9b7b8ee25af4 (current diff)
children 8cb3403430bb 55736bd691e2
files
diffstat 17 files changed, 373 insertions(+), 79 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sun May 03 17:59:12 2009 +0000
+++ b/ChangeLog	Sun May 03 20:18:23 2009 +0000
@@ -31,6 +31,8 @@
 	* Support most recent version of User Avatar. (XEP-0084 v1.1)
 	* Updated Entity Capabilities support. (Tobias Markmann)
 	* Better support for receiving remote users' nicknames.
+	* /affiliate and /role will now list the room members with the specified
+	  affiliation/role if possible. (Andrei Mozzhuhin)
 
 	Yahoo:
 	* P2P file transfers. (Sulabh Mahajan)
--- a/ChangeLog.API	Sun May 03 17:59:12 2009 +0000
+++ b/ChangeLog.API	Sun May 03 20:18:23 2009 +0000
@@ -13,6 +13,8 @@
 			* account-actions-changed
 			* account-created
 			* account-destroying
+		* blist-node-added and blist-node-removed signals (see
+		  blist-signals.dox)
 		* purple_buddy_destroy
 		* purple_buddy_get_protocol_data
 		* purple_buddy_set_protocol_data
@@ -54,6 +56,7 @@
 		  xmlnode_remove_with_namespace.
 
 		Deprecated:
+		* buddy-added and buddy-removed blist signals
 		* purple_buddy_get_local_alias
 		* purple_notify_user_info_remove_entry
 		* purple_status_type_set_primary_attr
--- a/doc/blist-signals.dox	Sun May 03 17:59:12 2009 +0000
+++ b/doc/blist-signals.dox	Sun May 03 20:18:23 2009 +0000
@@ -74,13 +74,30 @@
    a GList of PurpleBlistNodeAction's allowing a plugin to add menu items
  @endsignaldef
 
+ @signaldef blist-node-added
+  @signalproto
+void (*blist_node_added)(PurpleBlistNode *node)
+  @endsignalproto
+  @signaldesc
+   Emitted when a new blist node is added to the buddy list.
+ @endsignaldef
+
+ @signaldef blist-node-removed
+  @signalproto
+void (*blist_node_removed)(PurpleBlistNode *node)
+  @endsignalproto
+  @signaldesc
+   Emitted when a blist node is removed from the buddy list.
+ @endsignaldef
+
  @signaldef buddy-added
   @signalproto
 void (*buddy_added)(PurpleBuddy *buddy)
   @endsignalproto
   @signaldesc
    Emitted when a new buddy is added to the buddy list.
-  @endsignaldef
+  @deprecated Use blist-node-added instead.
+ @endsignaldef
 
  @signaldef buddy-removed
   @signalproto
@@ -88,7 +105,8 @@
   @endsignalproto
   @signaldesc
    Emitted when a buddy is removed from the buddy list.
-  @endsignaldef
+  @deprecated Use blist-node-removed instead.
+ @endsignaldef
 
  @signaldef buddy-icon-changed
   @signalproto
--- a/libpurple/blist.c	Sun May 03 17:59:12 2009 +0000
+++ b/libpurple/blist.c	Sun May 03 20:18:23 2009 +0000
@@ -1468,6 +1468,9 @@
 
 	if (ops && ops->update)
 		ops->update(purplebuddylist, (PurpleBlistNode *)cnode);
+
+	purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
+			cnode);
 }
 
 void purple_blist_add_buddy(PurpleBuddy *buddy, PurpleContact *contact, PurpleGroup *group, PurpleBlistNode *node)
@@ -1615,6 +1618,9 @@
 
 	/* Signal that the buddy has been added */
 	purple_signal_emit(purple_blist_get_handle(), "buddy-added", buddy);
+
+	purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
+			PURPLE_BLIST_NODE(buddy));
 }
 
 PurpleContact *purple_contact_new()
@@ -1952,6 +1958,9 @@
 		for (node = gnode->child; node; node = node->next)
 			ops->update(purplebuddylist, node);
 	}
+
+	purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
+			gnode);
 }
 
 void purple_blist_remove_contact(PurpleContact *contact)
@@ -1994,6 +2003,9 @@
 		if (ops && ops->remove)
 			ops->remove(purplebuddylist, node);
 
+		purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
+				PURPLE_BLIST_NODE(contact));
+
 		/* Delete the node */
 		purple_contact_destroy(contact);
 	}
@@ -2066,6 +2078,9 @@
 	/* Signal that the buddy has been removed before freeing the memory for it */
 	purple_signal_emit(purple_blist_get_handle(), "buddy-removed", buddy);
 
+	purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
+			PURPLE_BLIST_NODE(buddy));
+
 	purple_buddy_destroy(buddy);
 
 	/* If the contact is empty then remove it */
@@ -2109,6 +2124,9 @@
 	if (ops && ops->remove)
 		ops->remove(purplebuddylist, node);
 
+	purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
+			PURPLE_BLIST_NODE(chat));
+
 	/* Delete the node */
 	purple_chat_destroy(chat);
 }
@@ -2141,6 +2159,9 @@
 	if (ops && ops->remove)
 		ops->remove(purplebuddylist, node);
 
+	purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
+			PURPLE_BLIST_NODE(group));
+
 	/* Remove the group from all accounts that are online */
 	for (l = purple_connections_get_all(); l != NULL; l = l->next)
 	{
@@ -2973,6 +2994,16 @@
 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
 										PURPLE_SUBTYPE_BLIST_BUDDY));
 
+	purple_signal_register(handle, "blist-node-added",
+						 purple_marshal_VOID__POINTER, NULL, 1,
+						 purple_value_new(PURPLE_TYPE_SUBTYPE,
+										PURPLE_SUBTYPE_BLIST_NODE));
+
+	purple_signal_register(handle, "blist-node-removed",
+						 purple_marshal_VOID__POINTER, NULL, 1,
+						 purple_value_new(PURPLE_TYPE_SUBTYPE,
+										PURPLE_SUBTYPE_BLIST_NODE));
+
 	purple_signal_register(handle, "buddy-added",
 						 purple_marshal_VOID__POINTER, NULL, 1,
 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
--- a/libpurple/plugins/signals-test.c	Sun May 03 17:59:12 2009 +0000
+++ b/libpurple/plugins/signals-test.c	Sun May 03 20:18:23 2009 +0000
@@ -145,16 +145,39 @@
 }
 
 static void
-buddy_added_cb(PurpleBuddy *buddy, void *data)
+blist_node_added_cb(PurpleBlistNode *bnode, void *data)
 {
-	purple_debug_misc("signals test", "buddy_added_cb (%s)\n",
-	                  purple_buddy_get_name(buddy));
+	const char *name;
+	if (PURPLE_BLIST_NODE_IS_GROUP(bnode))
+		name = purple_group_get_name(PURPLE_GROUP(bnode));
+	else if (PURPLE_BLIST_NODE_IS_CONTACT(bnode))
+		/* Close enough */
+		name = purple_contact_get_alias(PURPLE_CONTACT(bnode));
+	else if (PURPLE_BLIST_NODE_IS_BUDDY(bnode))
+		name = purple_buddy_get_name(PURPLE_BUDDY(bnode));
+	else
+		name = "(unknown)";
+
+	purple_debug_misc("signals test", "blist_node_added_cb (%s)\n",
+	                  name ? name : "(null)");
 }
 
 static void
-buddy_removed_cb(PurpleBuddy *buddy, void *data)
+blist_node_removed_cb(PurpleBlistNode *bnode, void *data)
 {
-	purple_debug_misc("signals test", "buddy_removed_cb (%s)\n", purple_buddy_get_name(buddy));
+	const char *name;
+	if (PURPLE_BLIST_NODE_IS_GROUP(bnode))
+		name = purple_group_get_name(PURPLE_GROUP(bnode));
+	else if (PURPLE_BLIST_NODE_IS_CONTACT(bnode))
+		/* Close enough */
+		name = purple_contact_get_alias(PURPLE_CONTACT(bnode));
+	else if (PURPLE_BLIST_NODE_IS_BUDDY(bnode))
+		name = purple_buddy_get_name(PURPLE_BUDDY(bnode));
+	else
+		name = "(unknown)";
+
+	purple_debug_misc("signals test", "blist_node_removed_cb (%s)\n",
+	                  name ? name : "(null)");
 }
 
 static void
@@ -643,10 +666,10 @@
 						plugin, PURPLE_CALLBACK(buddy_signed_on_cb), NULL);
 	purple_signal_connect(blist_handle, "buddy-signed-off",
 						plugin, PURPLE_CALLBACK(buddy_signed_off_cb), NULL);
-	purple_signal_connect(blist_handle, "buddy-added",
-						plugin, PURPLE_CALLBACK(buddy_added_cb), NULL);
-	purple_signal_connect(blist_handle, "buddy-removed",
-						plugin, PURPLE_CALLBACK(buddy_removed_cb), NULL);
+	purple_signal_connect(blist_handle, "blist-node-added",
+						plugin, PURPLE_CALLBACK(blist_node_added_cb), NULL);
+	purple_signal_connect(blist_handle, "blist-node-removed",
+						plugin, PURPLE_CALLBACK(blist_node_removed_cb), NULL);
 	purple_signal_connect(blist_handle, "buddy-icon-changed",
 						plugin, PURPLE_CALLBACK(buddy_icon_changed_cb), NULL);
 	purple_signal_connect(blist_handle, "blist-node-aliased",
--- a/libpurple/protocols/jabber/caps.c	Sun May 03 17:59:12 2009 +0000
+++ b/libpurple/protocols/jabber/caps.c	Sun May 03 20:18:23 2009 +0000
@@ -349,7 +349,7 @@
 	}
 	g_hash_table_destroy(capstable);
 	g_hash_table_destroy(nodetable);
-	capstable = NULL;
+	capstable = nodetable = NULL;
 }
 
 typedef struct _jabber_caps_cbplususerdata {
--- a/libpurple/protocols/jabber/chat.c	Sun May 03 17:59:12 2009 +0000
+++ b/libpurple/protocols/jabber/chat.c	Sun May 03 20:18:23 2009 +0000
@@ -916,6 +916,68 @@
 	return TRUE;
 }
 
+static void
+jabber_chat_affiliation_list_cb(JabberStream *js, const char *from,
+                                JabberIqType type, const char *id,
+                                xmlnode *packet, gpointer data)
+{
+	JabberChat *chat;
+	xmlnode *query, *item;
+	int chat_id = GPOINTER_TO_INT(data);
+	GString *buf;
+
+	if(!(chat = jabber_chat_find_by_id(js, chat_id)))
+		return;
+
+	if (type == JABBER_IQ_ERROR)
+		return;
+
+	if(!(query = xmlnode_get_child(packet, "query")))
+		return;
+
+	buf = g_string_new(_("Affiliations:"));
+
+	item = xmlnode_get_child(query, "item");
+	if (item) {
+		for( ; item; item = xmlnode_get_next_twin(item)) {
+			const char *jid = xmlnode_get_attrib(item, "jid");
+			const char *affiliation = xmlnode_get_attrib(item, "affiliation");
+			if (jid && affiliation)
+				g_string_append_printf(buf, "\n%s %s", jid, affiliation);
+		}
+    } else {
+		buf = g_string_append_c(buf, '\n');
+		buf = g_string_append_len(buf, _("No users found"), -1);
+	}
+
+	purple_conv_chat_write(PURPLE_CONV_CHAT(chat->conv), "", buf->str,
+    	PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG, time(NULL));
+
+	g_string_free(buf, TRUE);
+}
+
+gboolean jabber_chat_affiliation_list(JabberChat *chat, const char *affiliation)
+{
+	JabberIq *iq;
+	char *room_jid;
+	xmlnode *query, *item;
+
+	iq = jabber_iq_new_query(chat->js, JABBER_IQ_GET,
+			"http://jabber.org/protocol/muc#admin");
+
+	room_jid = g_strdup_printf("%s@%s", chat->room, chat->server);
+	xmlnode_set_attrib(iq->node, "to", room_jid);
+
+	query = xmlnode_get_child(iq->node, "query");
+	item = xmlnode_new_child(query, "item");
+	xmlnode_set_attrib(item, "affiliation", affiliation);
+
+	jabber_iq_set_callback(iq, jabber_chat_affiliation_list_cb, GINT_TO_POINTER(chat->id));
+	jabber_iq_send(iq);
+
+	return TRUE;
+}
+
 gboolean jabber_chat_role_user(JabberChat *chat, const char *who, const char *role)
 {
 	char *to;
@@ -945,6 +1007,67 @@
 	return TRUE;
 }
 
+static void jabber_chat_role_list_cb(JabberStream *js, const char *from,
+                                     JabberIqType type, const char *id,
+                                     xmlnode *packet, gpointer data)
+{
+	JabberChat *chat;
+	xmlnode *query, *item;
+	int chat_id = GPOINTER_TO_INT(data);
+	GString *buf;
+
+	if(!(chat = jabber_chat_find_by_id(js, chat_id)))
+		return;
+
+	if (type == JABBER_IQ_ERROR)
+		return;
+
+	if(!(query = xmlnode_get_child(packet, "query")))
+		return;
+
+	buf = g_string_new(_("Roles:"));
+
+	item = xmlnode_get_child(query, "item");
+	if (item) {
+		for( ; item; item = xmlnode_get_next_twin(item)) {
+			const char *jid  = xmlnode_get_attrib(item, "jid");
+			const char *role = xmlnode_get_attrib(item, "role");
+			if (jid && role)
+				g_string_append_printf(buf, "\n%s %s", jid, role);
+	    }
+	} else {
+		buf = g_string_append_c(buf, '\n');
+		buf = g_string_append_len(buf, _("No users found"), -1);
+	}
+
+	purple_conv_chat_write(PURPLE_CONV_CHAT(chat->conv), "", buf->str,
+    	PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG, time(NULL));
+
+	g_string_free(buf, TRUE);
+}
+
+gboolean jabber_chat_role_list(JabberChat *chat, const char *role)
+{
+	JabberIq *iq;
+	char *room_jid;
+	xmlnode *query, *item;
+
+	iq = jabber_iq_new_query(chat->js, JABBER_IQ_GET,
+			"http://jabber.org/protocol/muc#admin");
+
+	room_jid = g_strdup_printf("%s@%s", chat->room, chat->server);
+	xmlnode_set_attrib(iq->node, "to", room_jid);
+
+	query = xmlnode_get_child(iq->node, "query");
+	item = xmlnode_new_child(query, "item");
+	xmlnode_set_attrib(item, "role", role);
+
+	jabber_iq_set_callback(iq, jabber_chat_role_list_cb, GINT_TO_POINTER(chat->id));
+	jabber_iq_send(iq);
+
+	return TRUE;
+}
+
 gboolean jabber_chat_kick_user(JabberChat *chat, const char *who, const char *why)
 {
 	JabberIq *iq;
--- a/libpurple/protocols/jabber/chat.h	Sun May 03 17:59:12 2009 +0000
+++ b/libpurple/protocols/jabber/chat.h	Sun May 03 20:18:23 2009 +0000
@@ -81,8 +81,10 @@
 		const char *why);
 gboolean jabber_chat_affiliate_user(JabberChat *chat, const char *who,
 		const char *affiliation);
+gboolean jabber_chat_affiliation_list(JabberChat *chat, const char *affiliation);
 gboolean jabber_chat_role_user(JabberChat *chat, const char *who,
 		const char *role);
+gboolean jabber_chat_role_list(JabberChat *chat, const char *role);
 gboolean jabber_chat_kick_user(JabberChat *chat, const char *who,
 		const char *why);
 
--- a/libpurple/protocols/jabber/ibb.c	Sun May 03 17:59:12 2009 +0000
+++ b/libpurple/protocols/jabber/ibb.c	Sun May 03 20:18:23 2009 +0000
@@ -503,6 +503,8 @@
 {
 	jabber_ibb_sessions = g_hash_table_new(g_str_hash, g_str_equal);
 
+	jabber_add_feature(XEP_0047_NAMESPACE, NULL);
+
 	jabber_iq_register_handler("close", XEP_0047_NAMESPACE, jabber_ibb_parse);
 	jabber_iq_register_handler("data", XEP_0047_NAMESPACE, jabber_ibb_parse);
 	jabber_iq_register_handler("open", XEP_0047_NAMESPACE, jabber_ibb_parse);
--- a/libpurple/protocols/jabber/jabber.c	Sun May 03 17:59:12 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sun May 03 20:18:23 2009 +0000
@@ -70,6 +70,7 @@
 static PurplePlugin *my_protocol = NULL;
 GList *jabber_features = NULL;
 GList *jabber_identities = NULL;
+GSList *jabber_cmds = NULL;
 
 static void jabber_unregister_account_cb(JabberStream *js);
 static void try_srv_connect(JabberStream *js);
@@ -2590,21 +2591,32 @@
 {
 	JabberChat *chat = jabber_chat_find_by_conv(conv);
 
-	if (!chat || !args || !args[0] || !args[1])
+	if (!chat || !args || !args[0])
 		return PURPLE_CMD_RET_FAILED;
 
-	if (strcmp(args[1], "owner") != 0 &&
-	    strcmp(args[1], "admin") != 0 &&
-	    strcmp(args[1], "member") != 0 &&
-	    strcmp(args[1], "outcast") != 0 &&
-	    strcmp(args[1], "none") != 0) {
-		*error = g_strdup_printf(_("Unknown affiliation: \"%s\""), args[1]);
+	if (strcmp(args[0], "owner") != 0 &&
+	    strcmp(args[0], "admin") != 0 &&
+	    strcmp(args[0], "member") != 0 &&
+	    strcmp(args[0], "outcast") != 0 &&
+	    strcmp(args[0], "none") != 0) {
+		*error = g_strdup_printf(_("Unknown affiliation: \"%s\""), args[0]);
 		return PURPLE_CMD_RET_FAILED;
 	}
 
-	if (!jabber_chat_affiliate_user(chat, args[0], args[1])) {
-		*error = g_strdup_printf(_("Unable to affiliate user %s as \"%s\""), args[0], args[1]);
-		return PURPLE_CMD_RET_FAILED;
+	if (args[1]) {
+		int i;
+		char **nicks = g_strsplit(args[1], " ", -1);
+
+		for (i = 0; nicks[i]; ++i)
+			if (!jabber_chat_affiliate_user(chat, nicks[i], args[0])) {
+				*error = g_strdup_printf(_("Unable to affiliate user %s as \"%s\""), nicks[i], args[0]);
+				g_strfreev(nicks);
+				return PURPLE_CMD_RET_FAILED;
+			}
+
+		g_strfreev(nicks);
+	} else {
+		jabber_chat_affiliation_list(chat, args[0]);
 	}
 
 	return PURPLE_CMD_RET_OK;
@@ -2615,23 +2627,32 @@
 {
 	JabberChat *chat = jabber_chat_find_by_conv(conv);
 
-	if (!chat || !args || !args[0] || !args[1])
+	if (!chat || !args || !args[0])
 		return PURPLE_CMD_RET_FAILED;
 
-	if (strcmp(args[1], "moderator") != 0 &&
-	    strcmp(args[1], "participant") != 0 &&
-	    strcmp(args[1], "visitor") != 0 &&
-	    strcmp(args[1], "none") != 0) {
-		*error = g_strdup_printf(_("Unknown role: \"%s\""), args[1]);
+	if (strcmp(args[0], "moderator") != 0 &&
+	    strcmp(args[0], "participant") != 0 &&
+	    strcmp(args[0], "visitor") != 0 &&
+	    strcmp(args[0], "none") != 0) {
+		*error = g_strdup_printf(_("Unknown role: \"%s\""), args[0]);
 		return PURPLE_CMD_RET_FAILED;
 	}
 
-	if (!jabber_chat_role_user(chat, args[0], args[1])) {
-		*error = g_strdup_printf(_("Unable to set role \"%s\" for user: %s"),
-		                         args[1], args[0]);
-		return PURPLE_CMD_RET_FAILED;
+	if (args[1]) {
+		int i;
+		char **nicks = g_strsplit(args[1], " ", -1);
+
+		for (i = 0; nicks[i]; i++)
+			if (!jabber_chat_role_user(chat, nicks[i], args[0])) {
+				*error = g_strdup_printf(_("Unable to set role \"%s\" for user: %s"),
+										 args[0], nicks[i]);
+				return PURPLE_CMD_RET_FAILED;
+			}
+
+		g_strfreev(nicks);
+	} else {
+		jabber_chat_role_list(chat, args[0]);
 	}
-
 	return PURPLE_CMD_RET_OK;
 }
 
@@ -3139,89 +3160,126 @@
 
 void jabber_register_commands(void)
 {
-	purple_cmd_register("config", "", PURPLE_CMD_P_PRPL,
+	PurpleCmdId id;
+	id = purple_cmd_register("config", "", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
 	                  "prpl-jabber", jabber_cmd_chat_config,
 	                  _("config:  Configure a chat room."), NULL);
-	purple_cmd_register("configure", "", PURPLE_CMD_P_PRPL,
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("configure", "", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
 	                  "prpl-jabber", jabber_cmd_chat_config,
 	                  _("configure:  Configure a chat room."), NULL);
-	purple_cmd_register("nick", "s", PURPLE_CMD_P_PRPL,
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("nick", "s", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
 	                  "prpl-jabber", jabber_cmd_chat_nick,
 	                  _("nick &lt;new nickname&gt;:  Change your nickname."),
 	                  NULL);
-	purple_cmd_register("part", "s", PURPLE_CMD_P_PRPL,
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("part", "s", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
 	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
 	                  jabber_cmd_chat_part, _("part [room]:  Leave the room."),
 	                  NULL);
-	purple_cmd_register("register", "", PURPLE_CMD_P_PRPL,
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("register", "", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
 	                  "prpl-jabber", jabber_cmd_chat_register,
 	                  _("register:  Register with a chat room."), NULL);
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
 	/* XXX: there needs to be a core /topic cmd, methinks */
-	purple_cmd_register("topic", "s", PURPLE_CMD_P_PRPL,
+	id = purple_cmd_register("topic", "s", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
 	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
 	                  jabber_cmd_chat_topic,
 	                  _("topic [new topic]:  View or change the topic."),
 	                  NULL);
-	purple_cmd_register("ban", "ws", PURPLE_CMD_P_PRPL,
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("ban", "ws", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
 	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
 	                  jabber_cmd_chat_ban,
 	                  _("ban &lt;user&gt; [reason]:  Ban a user from the room."),
 	                  NULL);
-	purple_cmd_register("affiliate", "ws", PURPLE_CMD_P_PRPL,
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("affiliate", "ws", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
 	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
 	                  jabber_cmd_chat_affiliate,
-	                  _("affiliate &lt;user&gt; &lt;owner|admin|member|outcast|none&gt;: Set a user's affiliation with the room."),
+	                  _("affiliate &lt;owner|admin|member|outcast|none&gt; [nick1] [nick2] ...: Get the users with an affiliation or set users' affiliation with the room."),
 	                  NULL);
-	purple_cmd_register("role", "ws", PURPLE_CMD_P_PRPL,
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("role", "ws", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
 	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
 	                  jabber_cmd_chat_role,
-	                  _("role &lt;user&gt; &lt;moderator|participant|visitor|none&gt;: Set a user's role in the room."),
+	                  _("role &lt;moderator|participant|visitor|none&gt; [nick1] [nick2] ...: Get the users with an role or set users' role with the room."),
 	                  NULL);
-	purple_cmd_register("invite", "ws", PURPLE_CMD_P_PRPL,
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("invite", "ws", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
 	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
 	                  jabber_cmd_chat_invite,
 	                  _("invite &lt;user&gt; [message]:  Invite a user to the room."),
 	                  NULL);
-	purple_cmd_register("join", "ws", PURPLE_CMD_P_PRPL,
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("join", "ws", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
 	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
 	                  jabber_cmd_chat_join,
 	                  _("join: &lt;room&gt; [password]:  Join a chat on this server."),
 	                  NULL);
-	purple_cmd_register("kick", "ws", PURPLE_CMD_P_PRPL,
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("kick", "ws", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
 	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
 	                  jabber_cmd_chat_kick,
 	                  _("kick &lt;user&gt; [reason]:  Kick a user from the room."),
 	                  NULL);
-	purple_cmd_register("msg", "ws", PURPLE_CMD_P_PRPL,
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("msg", "ws", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
 	                  "prpl-jabber", jabber_cmd_chat_msg,
 	                  _("msg &lt;user&gt; &lt;message&gt;:  Send a private message to another user."),
 	                  NULL);
-	purple_cmd_register("ping", "w", PURPLE_CMD_P_PRPL,
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("ping", "w", PURPLE_CMD_P_PRPL,
 					  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM |
 					  PURPLE_CMD_FLAG_PRPL_ONLY,
 					  "prpl-jabber", jabber_cmd_ping,
 					  _("ping &lt;jid&gt;:	Ping a user/component/server."),
 					  NULL);
-	purple_cmd_register("buzz", "w", PURPLE_CMD_P_PRPL,
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+
+	id = purple_cmd_register("buzz", "w", PURPLE_CMD_P_PRPL,
 					  PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY |
 					  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
 					  "prpl-jabber", jabber_cmd_buzz,
 					  _("buzz: Buzz a user to get their attention"), NULL);
 }
 
+void jabber_unregister_commands(void)
+{
+	while (jabber_cmds != NULL) {
+		purple_cmd_unregister(GPOINTER_TO_UINT(jabber_cmds->data));
+		jabber_cmds = g_slist_delete_link(jabber_cmds, jabber_cmds);
+	}
+}
+
 /* IPC functions */
 
 /**
@@ -3297,6 +3355,12 @@
 	jabber_add_feature("http://jabber.org/protocol/xhtml-im", 0);
 	jabber_add_feature("urn:xmpp:ping", 0);
 
+	/* Buzz/Attention */
+	jabber_add_feature(XEP_0224_NAMESPACE, jabber_buzz_isenabled);
+
+	/* Bits Of Binary */
+	jabber_add_feature(XEP_0231_NAMESPACE, jabber_custom_smileys_isenabled);
+
 	/* Jingle features! */
 	jabber_add_feature(JINGLE, 0);
 	jabber_add_feature(JINGLE_TRANSPORT_RAWUDP, 0);
--- a/libpurple/protocols/jabber/jabber.h	Sun May 03 17:59:12 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Sun May 03 20:18:23 2009 +0000
@@ -366,6 +366,7 @@
 PurpleMediaCaps jabber_get_media_caps(PurpleAccount *account, const char *who);
 
 void jabber_register_commands(void);
+void jabber_unregister_commands(void);
 
 void jabber_init_plugin(PurplePlugin *plugin);
 void jabber_uninit_plugin(void);
--- a/libpurple/protocols/jabber/libxmpp.c	Sun May 03 17:59:12 2009 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Sun May 03 20:18:23 2009 +0000
@@ -161,6 +161,8 @@
 	jabber_caps_uninit();
 	jabber_iq_uninit();
 
+	jabber_unregister_commands();
+
 	/* Stay on target...stay on target... Almost there... */
 	jabber_uninit_plugin();
 
@@ -298,10 +300,6 @@
 
 	jabber_ibb_init();
 	jabber_si_init();
-
-	jabber_add_feature(XEP_0224_NAMESPACE, jabber_buzz_isenabled);
-	jabber_add_feature(XEP_0231_NAMESPACE, jabber_custom_smileys_isenabled);
-	jabber_add_feature(XEP_0047_NAMESPACE, NULL);
 }
 
 
--- a/libpurple/protocols/jabber/pep.c	Sun May 03 17:59:12 2009 +0000
+++ b/libpurple/protocols/jabber/pep.c	Sun May 03 20:18:23 2009 +0000
@@ -44,7 +44,10 @@
 }
 
 void jabber_pep_uninit(void) {
-	/* any PEP handlers that need to clean things up go here */
+	/* any PEP handlers that need to clean things up go here. The standard
+	 * cleanup of removing the handler and feature are handled here and by
+	 * jabber_features_destroy() in jabber.c
+	 */
 	g_hash_table_destroy(pep_handlers);
 	pep_handlers = NULL;
 }
--- a/libpurple/protocols/jabber/useravatar.c	Sun May 03 17:59:12 2009 +0000
+++ b/libpurple/protocols/jabber/useravatar.c	Sun May 03 20:18:23 2009 +0000
@@ -45,6 +45,9 @@
 static void
 remove_avatar_0_12_nodes(JabberStream *js)
 {
+#if 0
+	See note below for why this is #if 0d
+
 	/* Publish an empty avatar according to the XEP-0084 v0.12 semantics */
 	xmlnode *publish, *item, *metadata;
 	/* publish the metadata */
@@ -61,16 +64,26 @@
 
 	/* publish */
 	jabber_pep_publish(js, publish);
+#endif
 
 	/*
-	 * This causes ejabberd 2.0.0 to RST our connection unceremoniously,
-	 * so disable it for now (we publish a <stop/> to the metadata node
-	 * instead.
+	 * This causes ejabberd 2.0.0 to kill the connection unceremoniously.
+	 * See https://support.process-one.net/browse/EJAB-623. When adiumx.com
+	 * was upgraded, the issue went away.
+	 *
+	 * I think it makes a lot of sense to not have an avatar at the old
+	 * node instead of having something interpreted as "no avatar". When
+	 * a contact with an older client logs in, in the latter situation,
+	 * there's a race between interpreting the <presence/> vcard-temp:x:update
+	 * avatar (non-empty) and the XEP-0084 v0.12 avatar (empty, so show no
+	 * avatar for the buddy) which leads to unhappy and confused users.
+	 *
+	 * A deluge of frustrating "Read error" bug reports may change my mind
+	 * about this.
+	 * --darkrain42
 	 */
-#if 0
 	jabber_pep_delete_node(js, NS_AVATAR_0_12_METADATA);
 	jabber_pep_delete_node(js, NS_AVATAR_0_12_DATA);
-#endif
 }
 
 void jabber_avatar_set(JabberStream *js, PurpleStoredImage *img)
--- a/pidgin/gtkconv.c	Sun May 03 17:59:12 2009 +0000
+++ b/pidgin/gtkconv.c	Sun May 03 20:18:23 2009 +0000
@@ -4496,19 +4496,26 @@
 }
 
 static void
-buddy_added_cb(PurpleBuddy *buddy, PurpleConversation *conv)
-{
-	buddy_cb_common(buddy, conv, TRUE);
-}
-
-static void
-buddy_removed_cb(PurpleBuddy *buddy, PurpleConversation *conv)
-{
+buddy_added_cb(PurpleBlistNode *node, PurpleConversation *conv)
+{
+	if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
+		return;
+
+	buddy_cb_common(PURPLE_BUDDY(node), conv, TRUE);
+}
+
+static void
+buddy_removed_cb(PurpleBlistNode *node, PurpleConversation *conv)
+{
+	if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
+		return;
+
 	/* If there's another buddy for the same "dude" on the list, do nothing. */
-	if (purple_find_buddy(buddy->account, buddy->name) != NULL)
+	if (purple_find_buddy(purple_buddy_get_account(PURPLE_BUDDY(node)),
+		                  purple_buddy_get_name(PURPLE_BUDDY(node))) != NULL)
 		return;
 
-	buddy_cb_common(buddy, conv, FALSE);
+	buddy_cb_common(PURPLE_BUDDY(node), conv, FALSE);
 }
 
 static void send_menu_cb(GtkWidget *widget, PidginConversation *gtkconv)
@@ -4747,9 +4754,9 @@
 	                                               "weight", CHAT_USERS_WEIGHT_COLUMN,
 	                                               NULL);
 
-	purple_signal_connect(blist_handle, "buddy-added",
+	purple_signal_connect(blist_handle, "blist-node-added",
 						gtkchat, PURPLE_CALLBACK(buddy_added_cb), conv);
-	purple_signal_connect(blist_handle, "buddy-removed",
+	purple_signal_connect(blist_handle, "blist-node-removed",
 						gtkchat, PURPLE_CALLBACK(buddy_removed_cb), conv);
 	purple_signal_connect(blist_handle, "blist-node-aliased",
 						gtkchat, PURPLE_CALLBACK(blist_node_aliased_cb), conv);
@@ -5121,7 +5128,8 @@
 	GList *list;
 
 	g_return_if_fail(bnode);
-	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(bnode));
+	if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
+		return;
 
 	for (list = pidgin_conv_windows_get_list(); list; list = list->next)
 	{
@@ -8017,9 +8025,9 @@
                         handle, PURPLE_CALLBACK(account_status_changed_cb), NULL);
 
 	/* Callbacks to update a conversation */
-	purple_signal_connect(blist_handle, "buddy-added", handle,
+	purple_signal_connect(blist_handle, "blist-node-added", handle,
 						G_CALLBACK(buddy_update_cb), NULL);
-	purple_signal_connect(blist_handle, "buddy-removed", handle,
+	purple_signal_connect(blist_handle, "blist-node-removed", handle,
 						G_CALLBACK(buddy_update_cb), NULL);
 	purple_signal_connect(blist_handle, "buddy-signed-on",
 						handle, PURPLE_CALLBACK(update_buddy_sign), "on");
--- a/pidgin/pixmaps/emotes/default/24/default.theme.in	Sun May 03 17:59:12 2009 +0000
+++ b/pidgin/pixmaps/emotes/default/24/default.theme.in	Sun May 03 20:18:23 2009 +0000
@@ -399,13 +399,16 @@
 smile-big.png             :D    :-D    =D
 wink.png                  ;)    ;-)    ;^)
 shock.png                 :-o
-tongue.png                :P    :-P
+tongue.png                :P    :-P    :-p
 glasses-cool.png          B-)
 angry.png                 X-(
-sad.png                   :(
+sad.png                   :(    :-(    =(
 crying.png                :'(
 neutral.png               :-|
 thinking.png              :-/
 love.png                  <3
 monkey.png                :(|)
 victory.png               \\m/
+! skywalker.png     C:-)    c:-)    C:)     c:)
+! monkey.png        :-(|)  :(|)     8-|)
+! cyclops.png       O-) o-)
--- a/pidgin/pixmaps/emotes/small/16/small.theme.in	Sun May 03 17:59:12 2009 +0000
+++ b/pidgin/pixmaps/emotes/small/16/small.theme.in	Sun May 03 20:18:23 2009 +0000
@@ -161,10 +161,10 @@
 smile-big.png             :D    :-D    =D
 wink.png                  ;)    ;-)    ;^)
 shock.png                 :-o
-tongue.png                :P    :-P
+tongue.png                :P    :-P    :-p
 glasses-cool.png          B-)
 angry.png                 X-(
-sad.png                   :(
+sad.png                   :(    :-(    =(
 crying.png                :'(
 neutral.png               :-|
 thinking.png              :-/