changeset 24685:0ac34748464a

merge of '17b584b23aee26cc5553b12483ab9b550eee8103' and '2b3429067be9bc2eb9be9e8deeeac9c35001f61d'
author John Bailey <rekkanoryo@rekkanoryo.org>
date Thu, 11 Dec 2008 06:24:14 +0000
parents 8792c97479fd (current diff) 662fdd4836aa (diff)
children 79da937ac0f1
files
diffstat 63 files changed, 2453 insertions(+), 1420 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Thu Dec 11 06:24:06 2008 +0000
+++ b/COPYRIGHT	Thu Dec 11 06:24:14 2008 +0000
@@ -418,6 +418,7 @@
 Brian Tarricone
 Peter Teichman
 Philip Tellis
+Michael Terry
 Arun A. Tharuvai
 Cestonaro Thilo
 Will Thompson
--- a/ChangeLog	Thu Dec 11 06:24:06 2008 +0000
+++ b/ChangeLog	Thu Dec 11 06:24:14 2008 +0000
@@ -8,7 +8,6 @@
 	  --with-system-ssl-certs and GnuTLS need to include these in the
 	  system certs directory.
 	* Corrected maximum message lengths for Yahoo!
-	* Enable auto-reply on Zephyr, to emulate 'zaway' (Toby Schaffer)
 	* The Buddy State Notification plugin no longer prints duplicate
 	  notifications when the same buddy is in multiple groups (Florian Quèze)
 	* The Buddy State Notification plugin no longer turns JID's, MSN Passport
@@ -16,10 +15,18 @@
 	* Fix a crash in SIMPLE when a malformed message is received.
 	* purple-remote now has a "getstatusmessage" command to retrieve the text
 	  of the current status message.
+	* Various fixes to the nullprpl (Paul Aurich)
+	* Fix a crash when accessing the roomlist for an account that's not
+	  connected (Paul Aurich)
+	* Fix a crash in purple_accounts_delete that happens when this function is
+	  called before the buddy list is initialized (Florian Quèze)
+	* On MSN, the Games and Office media can now be set and displayed (in
+	  addition to the previous Music media). The Media status text now shows
+	  the album, if possible. 
 
 	Gadu-Gadu:
 	* Fix some problems with Gadu-Gadu buddy icons (Adam Strzelecki)
-	* Gadu-Gadu now validates that UID's are valid (Adam Strzelecki)
+	* Gadu-Gadu now checks that UID's are valid (Adam Strzelecki)
 	* Gadu-Gadu now does proper charset translations where needed (Adam
 	  Strzelecki)
 
@@ -35,6 +42,13 @@
 	* Send "client-accepts-full-bind-result" attribute during SASL login.
 	  This will fix Google Talk login failures if the user configures the
 	  wrong domain for his/her account.
+	
+	Zephyr:
+	* Enable auto-reply, to emulate 'zaway' (Toby Schaffer)
+	* Fix a crash when an account is configured to use tzc but tzc is not
+	  installed or the configured tzc command is invalid (Michael Terry)
+	* Fix a 10 second delay waiting on tzc if it is not installed or the
+	  configured command is invalid (Michael Terry)
 
 	Pidgin:
 	* On GTK+ 2.14 and higher, we're using the gtk-tooltip-delay setting
@@ -47,6 +61,9 @@
 	      gtk-enable-tooltips = 0
 	* Moved the release notification dialog to a mini-dialog in the
 	  buddylist.  (Thanks to Casey Ho)
+	* Fix a crash when closing an authorization minidialog with the X then
+	  immediately going offline (Paul Aurich)
+	* Fix compatibility with old GTK+ yet again
 
 	Finch:
 	* Allow binding meta+arrow keys for actions.
--- a/ChangeLog.win32	Thu Dec 11 06:24:06 2008 +0000
+++ b/ChangeLog.win32	Thu Dec 11 06:24:14 2008 +0000
@@ -1,3 +1,7 @@
+version 2.5.3 (12/??/2008):
+	* Upgrade SILC to use the 1.1.8 toolkit
+	* Updated included Meanwhile library to include patch referenced in #7563
+
 version 2.5.2 (10/19/2008):
 	* Updated GTK+ to 2.12.12
 	  This will resolve an issue with stuff in QQ appearing as "(NULL)"
@@ -28,7 +32,7 @@
 
 version 2.4.0 (02/29/2008):
 	* Updated GTK+ to 2.12.8
-	* Updated include Meanwhile library to include patches referenced at:
+	* Updated included Meanwhile library to include patches referenced at:
 	  https://sourceforge.net/tracker/?func=detail&atid=656718&aid=1626349&group_id=110565
 	* Build the xmpp protocol with SASL support (and include Cyrus SASL
 	  2.1.22).
--- a/finch/gntaccount.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/finch/gntaccount.c	Thu Dec 11 06:24:14 2008 +0000
@@ -1069,6 +1069,8 @@
 			_("Authorize"), auth_cb,
 			_("Deny"), deny_cb);
 	}
+	g_signal_connect(G_OBJECT(uihandle), "destroy",
+		G_CALLBACK(purple_account_request_close), NULL);
 	g_free(buffer);
 	return uihandle;
 }
--- a/finch/gntroomlist.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/finch/gntroomlist.c	Thu Dec 11 06:24:14 2008 +0000
@@ -239,7 +239,8 @@
 		PurpleConnection *gc = list->data;
 
 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
-		if (prpl_info->roomlist_get_list != NULL) {
+		if (PURPLE_CONNECTION_IS_CONNECTED(gc) &&
+		        prpl_info->roomlist_get_list != NULL) {
 			PurpleAccount *account = purple_connection_get_account(gc);
 			char *text = g_strdup_printf("%s (%s)",
 					purple_account_get_username(account),
--- a/libpurple/account.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/account.c	Thu Dec 11 06:24:14 2008 +0000
@@ -2499,7 +2499,7 @@
 	purple_accounts_remove(account);
 
 	/* Remove this account's buddies */
-	for (gnode = purple_get_blist()->root; gnode != NULL; gnode = gnode->next) {
+	for (gnode = purple_blist_get_root(); gnode != NULL; gnode = gnode->next) {
 		if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
 			continue;
 
--- a/libpurple/protocols/bonjour/bonjour_ft.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/bonjour/bonjour_ft.c	Thu Dec 11 06:24:14 2008 +0000
@@ -411,8 +411,10 @@
 	BonjourData *bd;
 	PurpleXfer *xfer;
 
-	if(pc == NULL || packet == NULL || pb == NULL)
-		return;
+	g_return_if_fail(pc != NULL);
+	g_return_if_fail(packet != NULL);
+	g_return_if_fail(pb != NULL);
+
 	bd = (BonjourData*) pc->proto_data;
 	if(bd == NULL)
 		return;
@@ -488,8 +490,9 @@
 	xmlnode *query;
 	BonjourData *bd;
 
-	if(pc == NULL || packet == NULL || pb == NULL)
-		return;
+	g_return_if_fail(pc != NULL);
+	g_return_if_fail(packet != NULL);
+	g_return_if_fail(pb != NULL);
 
 	bd = (BonjourData*) pc->proto_data;
 	if(bd == NULL)
--- a/libpurple/protocols/bonjour/jabber.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/bonjour/jabber.c	Thu Dec 11 06:24:14 2008 +0000
@@ -77,7 +77,7 @@
 };
 
 static void
-xep_iq_parse(xmlnode *packet, PurpleConnection *connection, PurpleBuddy *pb);
+xep_iq_parse(xmlnode *packet, PurpleBuddy *pb);
 
 static BonjourJabberConversation *
 bonjour_jabber_conv_new(PurpleBuddy *pb, PurpleAccount *account, const char *ip) {
@@ -128,7 +128,7 @@
 
 	if (contents) {
 		char *bodystart = strchr(contents, '>');
-		char *bodyend = strrchr(bodystart, '<');
+		char *bodyend = bodystart ? strrchr(bodystart, '<') : NULL;
 		if (bodystart && bodyend && (bodystart + 1) != bodyend) {
 			*bodyend = '\0';
 			memmove(contents, bodystart + 1, (bodyend - bodystart));
@@ -364,11 +364,36 @@
 	if (!strcmp(packet->name, "message"))
 		_jabber_parse_and_write_message_to_ui(packet, pb);
 	else if(!strcmp(packet->name, "iq"))
-		xep_iq_parse(packet, NULL, pb);
+		xep_iq_parse(packet, pb);
 	else
 		purple_debug_warning("bonjour", "Unknown packet: %s\n", packet->name ? packet->name : "(null)");
 }
 
+static void bonjour_jabber_stream_ended(BonjourJabberConversation *bconv) {
+
+	/* Inform the user that the conversation has been closed */
+	BonjourBuddy *bb = NULL;
+
+	purple_debug_info("bonjour", "Recieved conversation close notification from %s.\n", bconv->pb ? bconv->pb->name : "(unknown)");
+
+	if(bconv->pb != NULL)
+		bb = bconv->pb->proto_data;
+#if 0
+	if(bconv->pb != NULL) {
+		PurpleConversation *conv;
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bconv->pb->name, bconv->pb->account);
+		if (conv != NULL) {
+			char *tmp = g_strdup_printf(_("%s has closed the conversation."), bconv->pb->name);
+			purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
+			g_free(tmp);
+		}
+	}
+#endif
+	/* Close the socket, clear the watcher and free memory */
+	bonjour_jabber_close_conversation(bconv);
+	if(bb)
+		bb->conversation = NULL;
+}
 
 static void
 _client_socket_handler(gpointer data, gint socket, PurpleInputCondition condition)
@@ -414,35 +439,6 @@
 	bonjour_parser_process(bconv, message, message_length);
 }
 
-void bonjour_jabber_stream_ended(BonjourJabberConversation *bconv) {
-
-	purple_debug_info("bonjour", "Recieved conversation close notification from %s.\n", bconv->pb ? bconv->pb->name : "(unknown)");
-
-	/* Inform the user that the conversation has been closed */
-	if (bconv != NULL) {
-		BonjourBuddy *bb = NULL;
-
-		if(bconv->pb != NULL)
-			bb = bconv->pb->proto_data;
-#if 0
-		if(bconv->pb != NULL) {
-			PurpleConversation *conv;
-			conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bconv->pb->name, bconv->pb->account);
-			if (conv != NULL) {
-				char *tmp = g_strdup_printf(_("%s has closed the conversation."), bconv->pb->name);
-				purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
-				g_free(tmp);
-			}
-		}
-#endif
-		/* Close the socket, clear the watcher and free memory */
-		bonjour_jabber_close_conversation(bconv);
-		if(bb)
-			bb->conversation = NULL;
-	}
-}
-
-
 struct _stream_start_data {
 	char *msg;
 };
@@ -1162,14 +1158,12 @@
 check_if_blocked(PurpleBuddy *pb)
 {
 	gboolean blocked = FALSE;
-	GSList *l = NULL;
-	PurpleAccount *acc = NULL;
+	GSList *l;
+	PurpleAccount *acc = purple_buddy_get_account(pb);
 
-	if(pb == NULL)
+	if(acc == NULL)
 		return FALSE;
 
-	acc = pb->account;
-
 	for(l = acc->deny; l != NULL; l = l->next) {
 		if(!purple_utf8_strcasecmp(pb->name, (char *)l->data)) {
 			purple_debug_info("bonjour", "%s has been blocked by %s.\n", pb->name, acc->username);
@@ -1181,25 +1175,19 @@
 }
 
 static void
-xep_iq_parse(xmlnode *packet, PurpleConnection *connection, PurpleBuddy *pb)
+xep_iq_parse(xmlnode *packet, PurpleBuddy *pb)
 {
-	xmlnode *child = NULL;
-
-	if(packet == NULL || pb == NULL)
-		return;
-
-	if(connection == NULL) {
-		if(pb->account != NULL)
-			connection = (pb->account)->gc;
-	}
+	xmlnode *child;
 
 	if(check_if_blocked(pb))
 		return;
 
 	if ((child = xmlnode_get_child(packet, "si")) || (child = xmlnode_get_child(packet, "error")))
-		xep_si_parse(connection, packet, pb);
+		xep_si_parse(purple_account_get_connection(pb->account),
+			packet, pb);
 	else
-		xep_bytestreams_parse(connection, packet, pb);
+		xep_bytestreams_parse(purple_account_get_connection(pb->account),
+			packet, pb);
 }
 
 int
--- a/libpurple/protocols/bonjour/jabber.h	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/bonjour/jabber.h	Thu Dec 11 06:24:14 2008 +0000
@@ -79,8 +79,6 @@
 
 void bonjour_jabber_stream_started(BonjourJabberConversation *bconv);
 
-void bonjour_jabber_stream_ended(BonjourJabberConversation *bconv);
-
 void bonjour_jabber_process_packet(PurpleBuddy *pb, xmlnode *packet);
 
 void bonjour_jabber_stop(BonjourJabber *data);
--- a/libpurple/protocols/jabber/jabber.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Thu Dec 11 06:24:14 2008 +0000
@@ -200,7 +200,7 @@
 		if (requested_resource != NULL) {
 			resource = xmlnode_new_child(bind, "resource");
 			xmlnode_insert_data(resource, requested_resource, -1);
-			free(requested_resource);
+			g_free(requested_resource);
 		}
 
 		jabber_iq_set_callback(iq, jabber_bind_result_cb, NULL);
--- a/libpurple/protocols/msn/contact.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/msn/contact.c	Thu Dec 11 06:24:14 2008 +0000
@@ -176,28 +176,6 @@
 	return 0;
 }
 
-/* get Network */
-/* QuLogic: These names don't really refer to the MsnNetwork,
- *          but I haven't yet written the code to properly use them.
- */
-static MsnNetwork
-msn_get_network(char *type)
-{
-	g_return_val_if_fail(type != NULL, 0);
-
-	if (!strcmp(type,"Regular")) {
-		return MSN_NETWORK_PASSPORT;
-	}
-	if (!strcmp(type,"Live")) {
-		return MSN_NETWORK_PASSPORT;
-	}
-	if (!strcmp(type,"LivePending")) {
-		return MSN_NETWORK_PASSPORT;
-	}
-
-	return MSN_NETWORK_UNKNOWN;
-}
-
 /* Create the AddressBook in the server, if we don't have one */
 static void
 msn_create_address_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data)
@@ -245,9 +223,26 @@
 	char *type = xmlnode_get_data(xmlnode_get_child(member, "Type"));
 	char *member_id = xmlnode_get_data(xmlnode_get_child(member, "MembershipId"));
 	MsnUser *user = msn_userlist_find_add_user(session->userlist, passport, NULL);
+	xmlnode *annotation;
+	guint nid = MSN_NETWORK_PASSPORT;
 
-	purple_debug_info("msn", "CL: %s name: %s, Type: %s, MembershipID: %s\n",
-		node, passport, type, member_id == NULL ? "(null)" : member_id);
+	for (annotation = xmlnode_get_child(member, "Annotations/Annotation");
+	     annotation;
+	     annotation = xmlnode_get_next_twin(annotation)) {
+		char *name = xmlnode_get_data(xmlnode_get_child(annotation, "Name"));
+		if (name && !strcmp(name, "MSN.IM.BuddyType")) {
+			char *value = xmlnode_get_data(xmlnode_get_child(annotation, "Value"));
+			if (value != NULL)
+				nid = strtoul(value, NULL, 10);
+			g_free(value);
+		}
+		g_free(name);
+	}
+ 
+	purple_debug_info("msn", "CL: %s name: %s, Type: %s, MembershipID: %s, NetworkID: %u\n",
+		node, passport, type, member_id == NULL ? "(null)" : member_id, nid);
+
+	msn_user_set_network(user, nid);
 
 	if (member_id) {
 		user->membership_id[list] = atoi(member_id);
@@ -445,16 +440,16 @@
 		if ((groupInfo = xmlnode_get_child(group, "groupInfo")) && (groupname = xmlnode_get_child(groupInfo, "name")))
 			group_name = xmlnode_get_data(groupname);
 
-		msn_group_new(session->userlist, group_id, group_name);
-
-		if (group_id == NULL){
+		if (group_id == NULL) {
 			/* Group of ungroupped buddies */
 			g_free(group_name);
 			continue;
 		}
 
+		msn_group_new(session->userlist, group_id, group_name);
+
 		purple_debug_info("msn", "AB group_id: %s, name: %s\n", group_id, group_name ? group_name : "(null)");
-		if ((purple_find_group(group_name)) == NULL){
+		if ((purple_find_group(group_name)) == NULL) {
 			PurpleGroup *g = purple_group_new(group_name);
 			purple_blist_add_group(g, NULL);
 		}
@@ -528,7 +523,6 @@
 		xmlnode *contactId, *contactInfo, *contactType, *passportName, *displayName, *guid, *groupIds, *messenger_user;
 		xmlnode *annotation;
 		MsnUser *user;
-		MsnNetwork networkId;
 
 		if (!(contactId = xmlnode_get_child(contactNode,"contactId"))
 				|| !(contactInfo = xmlnode_get_child(contactNode, "contactInfo"))
@@ -569,7 +563,6 @@
 			g_free(is_messenger_user);
 		}
 
-		networkId = msn_get_network(type);
 		passportName = xmlnode_get_child(contactInfo, "passportName");
 		if (passportName == NULL) {
 			xmlnode *emailsNode, *contactEmailNode, *emailNode;
@@ -600,7 +593,6 @@
 				if (msnEnabled && !strcmp(msnEnabled, "true")) {
 					/*Messenger enabled, Get the Passport*/
 					purple_debug_info("msn", "AB Yahoo User %s\n", passport ? passport : "(null)");
-					networkId = MSN_NETWORK_YAHOO;
 					g_free(msnEnabled);
 					break;
 				} else {
@@ -628,6 +620,15 @@
 			name = xmlnode_get_data(xmlnode_get_child(annotation, "Name"));
 			if (!strcmp(name, "AB.NickName"))
 				alias = xmlnode_get_data(xmlnode_get_child(annotation, "Value"));
+			else if (!strcmp(name, "MSN.IM.HasSharedFolder"))
+				; /* Do nothing yet... */
+			else if (!strcmp(name, "AB.Spouse"))
+				; /* Do nothing yet... */
+			else if (!strcmp(name, "MSN.Mobile.ContactId"))
+				; /* Do nothing yet... */
+			else
+				purple_debug_info("msn",
+				                  "Unknown AB contact annotation: %s\n", name);
 			g_free(name);
 		}
 
@@ -639,7 +640,6 @@
 
 		user = msn_userlist_find_add_user(session->userlist, passport, Name);
 		msn_user_set_uid(user, uid);
-		msn_user_set_network(user, networkId);
 		msn_user_set_mobile_phone(user, mobile_number);
 
 		groupIds = xmlnode_get_child(contactInfo, "groupIds");
@@ -870,7 +870,7 @@
 	gpointer data)
 {
 	MsnCallbackState *state = data;
-	xmlnode *faultcode;
+	xmlnode *fault;
 	char *faultcode_str;
 
 	if (resp == NULL) {
@@ -880,9 +880,9 @@
 		return;
 	}
 
-	faultcode = xmlnode_get_child(resp->xml, "Body/Fault/faultcode");
+	fault = xmlnode_get_child(resp->xml, "Body/Fault");
 
-	if (faultcode == NULL) {
+	if (fault == NULL) {
 		/* No errors */
 		if (state->cb)
 			((MsnSoapCallback)state->cb)(req, resp, data);
@@ -890,7 +890,7 @@
 		return;
 	}
 
-	faultcode_str = xmlnode_get_data(faultcode);
+	faultcode_str = xmlnode_get_data(xmlnode_get_child(fault, "faultcode"));
 
 	if (faultcode_str && g_str_equal(faultcode_str, "q0:BadContextToken")) {
 		purple_debug_info("msn",
@@ -903,12 +903,15 @@
 	}
 	else
 	{
-		/* We don't know how to respond to this faultcode, so just log it */
-		/* XXX: Probably should notify the user or undo the change or something? */
-		char *str = xmlnode_to_str(xmlnode_get_child(resp->xml, "Body/Fault"), NULL);
-		purple_debug_error("msn", "Operation {%s} Failed, SOAP Fault was: %s\n",
-		                   msn_contact_operation_str(state->action), str);
-		g_free(str);
+		if (state->cb) {
+			((MsnSoapCallback)state->cb)(req, resp, data);
+		} else {
+			/* We don't know how to respond to this faultcode, so log it */
+			char *str = xmlnode_to_str(fault, NULL);
+			purple_debug_error("msn", "Operation {%s} Failed, SOAP Fault was: %s\n",
+			                   msn_contact_operation_str(state->action), str);
+			g_free(str);
+		}
 		msn_callback_state_free(state);
 	}
 
@@ -943,21 +946,44 @@
 	MsnUser *user;
 	xmlnode *guid;
 
+	xmlnode *fault;
+
 	g_return_if_fail(session != NULL);
+	userlist = session->userlist;
+
+	fault = xmlnode_get_child(resp->xml, "Body/Fault");
+	if (fault != NULL) {
+		char *errorcode = xmlnode_get_data(xmlnode_get_child(fault, "detail/errorcode"));
+		if (errorcode && !strcmp(errorcode, "EmailDomainIsFederated")) {
+			/* Do something special! */
+			purple_debug_error("msn", "Contact is from a federated domain, but don't know what to do yet!\n");
 
-	userlist = session->userlist;
+		} else if (errorcode && !strcmp(errorcode, "InvalidPassportUser")) {
+			PurpleBuddy *buddy = purple_find_buddy(session->account, state->who);
+			char *str = g_strdup_printf(_("Unable to add \"%s\"."), state->who);
+			purple_notify_error(state->session, _("Buddy Add error"), str,
+			                    _("The username specified does not exist."));
+			g_free(str);
+			msn_userlist_rem_buddy(userlist, state->who);
+			if (buddy != NULL)
+				purple_blist_remove_buddy(buddy);
+
+		} else {
+			/* We don't know how to respond to this faultcode, so log it */
+			char *fault_str = xmlnode_to_str(fault, NULL);
+			if (fault_str != NULL) {
+				purple_debug_error("msn", "Operation {%s} Failed, SOAP Fault was: %s\n",
+				                   msn_contact_operation_str(state->action), fault_str);
+				g_free(fault_str);
+			}
+		}
+		return;
+	}
 
 	purple_debug_info("msn", "Contact added successfully\n");
 
-	/* the code this block is replacing didn't send ADL for yahoo contacts,
-	 * but i haven't confirmed this is WLM's behaviour wrt yahoo contacts
-	 */
-	if ( !msn_user_is_yahoo(session->account, state->who) ) {
-		msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_AL);
-		msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_FL);
-	}
-
-	msn_notification_send_fqy(session, state->who);
+	msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_AL);
+	msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_FL);
 
 	user = msn_userlist_find_add_user(userlist, state->who, state->who);
 	msn_user_add_group_id(user, state->guid);
@@ -976,6 +1002,7 @@
 void
 msn_add_contact(MsnSession *session, MsnCallbackState *state, const char *passport)
 {
+	MsnUser *user;
 	gchar *body = NULL;
 	gchar *contact_xml = NULL;
 
@@ -993,7 +1020,21 @@
 
 	purple_debug_info("msn", "Adding contact %s to contact list\n", passport);
 
-	contact_xml = g_strdup_printf(MSN_CONTACT_XML, passport);
+	user = msn_userlist_find_user(session->userlist, passport);
+	if (user == NULL) {
+		purple_debug_warning("msn", "Unable to retrieve user %s from the userlist!\n", passport);
+		return; /* guess this never happened! */
+	}
+
+	if (user->networkid != MSN_NETWORK_PASSPORT) {
+		contact_xml = g_strdup_printf(MSN_CONTACT_EMAIL_XML,
+		                              user->networkid == MSN_NETWORK_YAHOO ?
+		                                  "Messenger2" :
+		                                  "Messenger3",
+		                              passport, 0);
+	} else {
+		contact_xml = g_strdup_printf(MSN_CONTACT_XML, passport);
+	}
 	body = g_strdup_printf(MSN_ADD_CONTACT_TEMPLATE, contact_xml);
 
 	state->body = xmlnode_from_str(body, -1);
@@ -1011,11 +1052,41 @@
 	gpointer data)
 {
 	MsnCallbackState *state = data;
+	MsnSession *session = state->session;
 	MsnUserList *userlist;
+	xmlnode *fault;
+
+	g_return_if_fail(session != NULL);
+	userlist = session->userlist;
+
+	fault = xmlnode_get_child(resp->xml, "Body/Fault");
+	if (fault != NULL) {
+		char *errorcode = xmlnode_get_data(xmlnode_get_child(fault, "detail/errorcode"));
+		if (errorcode && !strcmp(errorcode, "EmailDomainIsFederated")) {
+			/* Do something special! */
+			purple_debug_error("msn", "Contact is from a federated domain, but don't know what to do yet!\n");
 
-	g_return_if_fail(data != NULL);
+		} else if (errorcode && !strcmp(errorcode, "InvalidPassportUser")) {
+			PurpleBuddy *buddy = purple_find_buddy(session->account, state->who);
+			char *str = g_strdup_printf(_("Unable to add \"%s\"."), state->who);
+			purple_notify_error(session, _("Buddy Add error"), str,
+			                    _("The username specified does not exist."));
+			g_free(str);
+			msn_userlist_rem_buddy(userlist, state->who);
+			if (buddy != NULL)
+				purple_blist_remove_buddy(buddy);
 
-	userlist = state->session->userlist;
+		} else {
+			/* We don't know how to respond to this faultcode, so log it */
+			char *fault_str = xmlnode_to_str(fault, NULL);
+			if (fault_str != NULL) {
+				purple_debug_error("msn", "Operation {%s} Failed, SOAP Fault was: %s\n",
+				                   msn_contact_operation_str(state->action), fault_str);
+				g_free(fault_str);
+			}
+		}
+		return;
+	}
 
 	if (msn_userlist_add_buddy_to_group(userlist, state->who,
 			state->new_group_name)) {
@@ -1036,11 +1107,8 @@
 			g_free(uid);
 		}
 
-		if ( !msn_user_is_yahoo(state->session->account, state->who) ) {
-			msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_AL);
-			msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_FL);
-		}
-		msn_notification_send_fqy(state->session, state->who);
+		msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_AL);
+		msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_FL);
 
 		if (msn_userlist_user_is_in_list(user, MSN_LIST_PL)) {
 			msn_del_contact_from_list(state->session, NULL, state->who, MSN_LIST_PL);
@@ -1095,8 +1163,14 @@
 		return; /* guess this never happened! */
 	}
 
-	if (user != NULL && user->uid != NULL) {
+	if (user->uid != NULL) {
 		contact_xml = g_strdup_printf(MSN_CONTACT_ID_XML, user->uid);
+	} else if (user->networkid != MSN_NETWORK_PASSPORT) {
+		contact_xml = g_strdup_printf(MSN_CONTACT_EMAIL_XML,
+		                              user->networkid == MSN_NETWORK_YAHOO ?
+		                                  "Messenger2" :
+		                                  "Messenger3",
+		                              passport, 0);
 	} else {
 		contact_xml = g_strdup_printf(MSN_CONTACT_XML, passport);
 	}
@@ -1120,6 +1194,17 @@
 	MsnCallbackState *state = data;
 	MsnUserList *userlist = state->session->userlist;
 	MsnUser *user = msn_userlist_find_user_with_id(userlist, state->uid);
+	xmlnode *fault;
+
+	/* We don't know how to respond to this faultcode, so log it */
+	fault = xmlnode_get_child(resp->xml, "Body/Fault");
+	if (fault != NULL) {
+		char *fault_str = xmlnode_to_str(fault, NULL);
+		purple_debug_error("msn", "Operation {%s} Failed, SOAP Fault was: %s\n",
+		                   msn_contact_operation_str(state->action), fault_str);
+		g_free(fault_str);
+		return;
+	}
 
 	purple_debug_info("msn", "Delete contact successful\n");
 
@@ -1140,8 +1225,8 @@
 		contact_id_xml = g_strdup_printf(MSN_CONTACT_ID_XML, user->uid);
 		purple_debug_info("msn", "Deleting contact with contactId: %s\n", user->uid);
 	} else {
-		contact_id_xml = g_strdup_printf(MSN_CONTACT_XML, user->passport);
-		purple_debug_info("msn", "Deleting contact with passport: %s\n", user->passport);
+		purple_debug_info("msn", "Unable to delete contact %s without a ContactId\n", user->passport);
+		return;
 	}
 
 	state = msn_callback_state_new(session);
@@ -1165,6 +1250,17 @@
 	gpointer data)
 {
 	MsnCallbackState *state = data;
+	xmlnode *fault;
+
+	/* We don't know how to respond to this faultcode, so log it */
+	fault = xmlnode_get_child(resp->xml, "Body/Fault");
+	if (fault != NULL) {
+		char *fault_str = xmlnode_to_str(fault, NULL);
+		purple_debug_error("msn", "Operation {%s} Failed, SOAP Fault was: %s\n",
+		                   msn_contact_operation_str(state->action), fault_str);
+		g_free(fault_str);
+		return;
+	}
 
 	if (msn_userlist_rem_buddy_from_group(state->session->userlist,
 			state->who, state->old_group_name)) {
@@ -1235,6 +1331,19 @@
 msn_update_contact_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
 	gpointer data)
 {
+	MsnCallbackState *state = (MsnCallbackState *)data;
+	xmlnode *fault;
+
+	/* We don't know how to respond to this faultcode, so log it */
+	fault = xmlnode_get_child(resp->xml, "Body/Fault");
+	if (fault != NULL) {
+		char *fault_str = xmlnode_to_str(fault, NULL);
+		purple_debug_error("msn", "Operation {%s} Failed, SOAP Fault was: %s\n",
+		                   msn_contact_operation_str(state->action), fault_str);
+		g_free(fault_str);
+		return;
+	}
+
 	purple_debug_info("msn", "Contact updated successfully\n");
 }
 
@@ -1312,6 +1421,17 @@
 {
 	MsnCallbackState *state = data;
 	MsnSession *session = state->session;
+	xmlnode *fault;
+
+	/* We don't know how to respond to this faultcode, so log it */
+	fault = xmlnode_get_child(resp->xml, "Body/Fault");
+	if (fault != NULL) {
+		char *fault_str = xmlnode_to_str(fault, NULL);
+		purple_debug_error("msn", "Operation {%s} Failed, SOAP Fault was: %s\n",
+		                   msn_contact_operation_str(state->action), fault_str);
+		g_free(fault_str);
+		return;
+	}
 
 	purple_debug_info("msn", "Contact %s deleted successfully from %s list on server!\n", state->who, MsnMemberRole[state->list_id]);
 
@@ -1339,10 +1459,13 @@
 			  const gchar *passport, const MsnListId list)
 {
 	gchar *body = NULL, *member = NULL;
+	const char *type = "PassportMember";
+	gchar *federate = NULL;
 	MsnSoapPartnerScenario partner_scenario;
 	MsnUser *user;
 
 	g_return_if_fail(session != NULL);
+	g_return_if_fail(session->userlist != NULL);
 	g_return_if_fail(passport != NULL);
 	g_return_if_fail(list < 5);
 
@@ -1354,21 +1477,27 @@
 	msn_callback_state_set_list_id(state, list);
 	msn_callback_state_set_who(state, passport);
 
+	user = msn_userlist_find_user(session->userlist, passport);
+	if (user && user->networkid != MSN_NETWORK_PASSPORT) {
+		type = "EmailMember";
+		federate = g_strdup_printf(MSN_MEMBER_FEDERATED_ANNOTATION_XML,
+		                           user->networkid);
+	}
+	
 	if (list == MSN_LIST_PL) {
-		g_return_if_fail(session->userlist != NULL);
-
-		user = msn_userlist_find_user(session->userlist, passport);
-
 		partner_scenario = MSN_PS_CONTACT_API;
-		member = g_strdup_printf(MSN_MEMBER_MEMBERSHIPID_XML, user->membership_id[MSN_LIST_PL]);
+		member = g_strdup_printf(MSN_MEMBER_MEMBERSHIPID_XML,
+		                         type, user->membership_id[MSN_LIST_PL],
+		                         federate ? federate : "");
 	} else {
 		/* list == MSN_LIST_AL || list == MSN_LIST_BL */
 		partner_scenario = MSN_PS_BLOCK_UNBLOCK;
-		
-		member = g_strdup_printf(MSN_MEMBER_PASSPORT_XML, passport);
+		member = g_strdup_printf(MSN_MEMBER_PASSPORT_XML,
+		                         type, passport,
+		                         federate ? federate : "");
 	}
 
-	body = g_strdup_printf(MSN_CONTACT_DELECT_FROM_LIST_TEMPLATE,
+	body = g_strdup_printf(MSN_CONTACT_DELETE_FROM_LIST_TEMPLATE,
 		MsnSoapPartnerScenarioText[partner_scenario],
 		MsnMemberRole[list], member);
 
@@ -1378,6 +1507,7 @@
 	state->cb = msn_del_contact_from_list_read_cb;
 	msn_contact_request(state);
 
+	g_free(federate);
 	g_free(member);
 	g_free(body);
 }
@@ -1387,8 +1517,18 @@
 	gpointer data)
 {
 	MsnCallbackState *state = data;
+	xmlnode *fault;
 
-	g_return_if_fail(state != NULL);
+	/* We don't know how to respond to this faultcode, so log it */
+	fault = xmlnode_get_child(resp->xml, "Body/Fault");
+	if (fault != NULL) {
+		char *fault_str = xmlnode_to_str(fault, NULL);
+		purple_debug_error("msn", "Operation {%s} Failed, SOAP Fault was: %s\n",
+		                   msn_contact_operation_str(state->action), fault_str);
+		g_free(fault_str);
+		return;
+	}
+
 	g_return_if_fail(state->session != NULL);
 
 	purple_debug_info("msn", "Contact %s added successfully to %s list on server!\n", state->who, MsnMemberRole[state->list_id]);
@@ -1415,7 +1555,10 @@
 			const gchar *passport, const MsnListId list)
 {
 	gchar *body = NULL, *member = NULL;
+	const char *type = "PassportMember";
+	gchar *federate = NULL;
 	MsnSoapPartnerScenario partner_scenario;
+	MsnUser *user;
 
 	g_return_if_fail(session != NULL);
 	g_return_if_fail(passport != NULL);
@@ -1429,9 +1572,16 @@
 	msn_callback_state_set_list_id(state, list);
 	msn_callback_state_set_who(state, passport);
 
-	partner_scenario = (list == MSN_LIST_RL) ? MSN_PS_CONTACT_API : MSN_PS_BLOCK_UNBLOCK;
+	user = msn_userlist_find_user(session->userlist, passport);
+	if (user && user->networkid != MSN_NETWORK_PASSPORT) {
+		type = "EmailMember";
+		federate = g_strdup_printf(MSN_MEMBER_FEDERATED_ANNOTATION_XML,
+		                           user->networkid);
+	}
 
-	member = g_strdup_printf(MSN_MEMBER_PASSPORT_XML, state->who);
+	partner_scenario = (list == MSN_LIST_RL) ? MSN_PS_CONTACT_API : MSN_PS_BLOCK_UNBLOCK;
+	member = g_strdup_printf(MSN_MEMBER_PASSPORT_XML,
+	                         type, state->who, federate ? federate : "");
 
 	body = g_strdup_printf(MSN_CONTACT_ADD_TO_LIST_TEMPLATE,
 		MsnSoapPartnerScenarioText[partner_scenario],
@@ -1443,6 +1593,7 @@
 	state->cb = msn_add_contact_to_list_read_cb;
 	msn_contact_request(state);
 
+	g_free(federate);
 	g_free(member);
 	g_free(body);
 }
@@ -1482,6 +1633,17 @@
 	MsnCallbackState *state = data;
 	MsnSession *session;
 	MsnUserList *userlist;
+	xmlnode *fault;
+
+	/* We don't know how to respond to this faultcode, so log it */
+	fault = xmlnode_get_child(resp->xml, "Body/Fault");
+	if (fault != NULL) {
+		char *fault_str = xmlnode_to_str(fault, NULL);
+		purple_debug_error("msn", "Operation {%s} Failed, SOAP Fault was: %s\n",
+		                   msn_contact_operation_str(state->action), fault_str);
+		g_free(fault_str);
+		return;
+	}
 
 	purple_debug_info("msn", "Group request successful.\n");
 
@@ -1661,3 +1823,4 @@
 	g_free(escaped_group_name);
 	g_free(body);
 }
+
--- a/libpurple/protocols/msn/contact.h	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/msn/contact.h	Thu Dec 11 06:24:14 2008 +0000
@@ -211,6 +211,27 @@
 		"</contactInfo>"\
 	"</Contact>"
 
+#define MSN_CONTACT_ID_XML \
+	"<Contact>"\
+		"<contactId>%s</contactId>"\
+	"</Contact>"
+
+#define MSN_CONTACT_EMAIL_XML \
+	"<Contact>"\
+		"<contactInfo>"\
+			"<emails>"\
+				"<ContactEmail>"\
+					"<contactEmailType>%s</contactEmailType>"\
+					"<email>%s</email>"\
+					"<isMessengerEnabled>true</isMessengerEnabled>"\
+					"<Capability>%d</Capability>"\
+					"<MessengerEnabledExternally>false</MessengerEnabledExternally>"\
+					"<propertiesChanged/>"\
+				"</ContactEmail>"\
+			"</emails>"\
+		"</contactInfo>"\
+	"</Contact>"
+
 #define MSN_ADD_CONTACT_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
 "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
 	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
@@ -275,7 +296,6 @@
 
 /* Delete a contact from the Contact List */
 #define MSN_CONTACT_DEL_SOAP_ACTION	"http://www.msn.com/webservices/AddressBook/ABContactDelete"
-#define MSN_CONTACT_ID_XML		"<Contact><contactId>%s</contactId></Contact>"
 #define MSN_DEL_CONTACT_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
 "<soap:Envelope"\
 	" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
@@ -376,22 +396,32 @@
 #define MSN_DELETE_MEMBER_FROM_LIST_SOAP_ACTION	"http://www.msn.com/webservices/AddressBook/DeleteMember"
 
 #define MSN_MEMBER_PASSPORT_XML	\
-	"<Member xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"PassportMember\">"\
+	"<Member xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"%s\">"\
 		"<Type>Passport</Type>"\
 		"<State>Accepted</State>"\
 		"<PassportName>%s</PassportName>"\
+		"%s"\
 	"</Member>"
 
 #define MSN_MEMBER_MEMBERSHIPID_XML	\
-	"<Member xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"PassportMember\">"\
+	"<Member xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"%s\">"\
 		"<Type>Passport</Type>"\
 		"<MembershipId>%u</MembershipId>"\
 		"<State>Accepted</State>"\
+		"%s"\
 	"</Member>"
 
+#define MSN_MEMBER_FEDERATED_ANNOTATION_XML \
+	"<Annotations>"\
+		"<Annotation>"\
+			"<Name>MSN.IM.BuddyType</Name>"\
+			"<Value>%02d:</Value>"\
+		"</Annotation>"\
+	"</Annotations>"
+
 /* first delete contact from allow list */
 
-#define MSN_CONTACT_DELECT_FROM_LIST_TEMPLATE "<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
+#define MSN_CONTACT_DELETE_FROM_LIST_TEMPLATE "<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
 "<soap:Envelope"\
 	" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
 	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
--- a/libpurple/protocols/msn/msn.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/msn/msn.c	Thu Dec 11 06:24:14 2008 +0000
@@ -647,25 +647,41 @@
 	presence = purple_buddy_get_presence(buddy);
 	status = purple_presence_get_active_status(presence);
 
-	/* I think status message should take precedence over media */
-	msg = purple_status_get_attr_string(status, "message");
-	if (msg && *msg)
-		return g_markup_escape_text(msg, -1);
-
 	if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) {
-		const char *title, *artist;
+		const char *title, *game, *office;
 		char *media, *esc;
 		status = purple_presence_get_status(presence, "tune");
 		title = purple_status_get_attr_string(status, PURPLE_TUNE_TITLE);
-		artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST);
-
-		media = g_strdup_printf("%s%s%s", title, artist ? " - " : "",
-				artist ? artist : "");
+
+		game = purple_status_get_attr_string(status, "game");
+		office = purple_status_get_attr_string(status, "office");
+
+		if (title && *title) {
+			const char *artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST);
+			const char *album = purple_status_get_attr_string(status, PURPLE_TUNE_ALBUM);
+			media = g_strdup_printf("%s%s%s%s%s%s", title,
+			                        (artist && *artist) ? " - " : "",
+			                        (artist && *artist) ? artist : "",
+			                        (album && *album) ? " (" : "",
+			                        (album && *album) ? album : "",
+			                        (album && *album) ? ")" : "");
+		}
+		else if (game && *game)
+			media = g_strdup_printf("Playing %s", game);
+		else if (office && *office)
+			media = g_strdup_printf("Editing %s", office);
+		else
+			return NULL;
 		esc = g_markup_escape_text(media, -1);
 		g_free(media);
 		return esc;
 	}
 
+	/* Official client says media takes precedence over message */
+	msg = purple_status_get_attr_string(status, "message");
+	if (msg && *msg)
+		return g_markup_escape_text(msg, -1);
+
 	return NULL;
 }
 
@@ -681,6 +697,7 @@
 	if (purple_presence_is_online(presence))
 	{
 		const char *psm, *name;
+		const char *mediatype = NULL;
 		char *currentmedia = NULL;
 		char *tmp;
 
@@ -688,10 +705,20 @@
 		if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) {
 			PurpleStatus *tune = purple_presence_get_status(presence, "tune");
 			const char *title = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE);
-			const char *artist = purple_status_get_attr_string(tune, PURPLE_TUNE_ARTIST);
-			const char *album = purple_status_get_attr_string(tune, PURPLE_TUNE_ALBUM);
-			currentmedia = purple_util_format_song_info(title, artist, album, NULL);
-			/* We could probably just use user->media.title etc. here */
+			const char *game = purple_status_get_attr_string(tune, "game");
+			const char *office = purple_status_get_attr_string(tune, "office");
+			if (title && *title) {
+				const char *artist = purple_status_get_attr_string(tune, PURPLE_TUNE_ARTIST);
+				const char *album = purple_status_get_attr_string(tune, PURPLE_TUNE_ALBUM);
+				mediatype = _("Now Listening");
+				currentmedia = purple_util_format_song_info(title, artist, album, NULL);
+			} else if (game && *game) {
+				mediatype = _("Playing a game");
+				currentmedia = g_strdup(game);
+			} else if (office && *office) {
+				mediatype = _("Working");
+				currentmedia = g_strdup(office);
+			}
 		}
 
 		if (!purple_status_is_available(status)) {
@@ -745,7 +772,7 @@
 		}
 
 		if (currentmedia) {
-			purple_notify_user_info_add_pair(user_info, _("Now Listening"), currentmedia);
+			purple_notify_user_info_add_pair(user_info, mediatype, currentmedia);
 			g_free(currentmedia);
 		}
 	}
@@ -840,6 +867,8 @@
 			PURPLE_TUNE_ARTIST, _("Artist"), purple_value_new(PURPLE_TYPE_STRING),
 			PURPLE_TUNE_ALBUM, _("Album"), purple_value_new(PURPLE_TYPE_STRING),
 			PURPLE_TUNE_TITLE, _("Title"), purple_value_new(PURPLE_TYPE_STRING),
+			"game", _("Game Title"), purple_value_new(PURPLE_TYPE_STRING),
+			"office", _("Office Title"), purple_value_new(PURPLE_TYPE_STRING),
 			NULL);
 	types = g_list_append(types, status);
 
@@ -1414,21 +1443,18 @@
 		purple_debug_info("msn", "msn_add_buddy: %s\n", who);
 #endif
 
-#if 0
-	/* Which is the max? */
-	if (session->fl_users_count >= 150)
-	{
-		purple_debug_info("msn", "Too many buddies\n");
-		/* Buddy list full */
-		/* TODO: purple should be notified of this */
-		return;
-	}
-#endif
-
 	/* XXX - Would group ever be NULL here?  I don't think so...
 	 * shx: Yes it should; MSN handles non-grouped buddies, and this is only
 	 * internal. */
-	msn_userlist_add_buddy(userlist, who, group ? group->name : NULL);
+	if (msn_userlist_find_user(userlist, who) != NULL) {
+		/* We already know this buddy. This function takes care of users
+		   already in the list and stuff... */
+		msn_userlist_add_buddy(userlist, who, group ? group->name : NULL);
+	} else {
+		/* We need to check the network for this buddy first */
+		msn_userlist_save_pending_buddy(userlist, who, group ? group->name : NULL);
+		msn_notification_send_fqy(session, who);
+	}
 }
 
 static void
@@ -1811,7 +1837,7 @@
 		if (b->server_alias)
 		{
 			char *nicktext = g_markup_escape_text(b->server_alias, -1);
-			tmp = g_strdup_printf("<font sml=\"msn\">%s</font><br>", nicktext);
+			tmp = g_strdup_printf("<font sml=\"msn\">%s</font>", nicktext);
 			purple_notify_user_info_add_pair(user_info, _("Nickname"), tmp);
 			g_free(tmp);
 			g_free(nicktext);
@@ -1917,9 +1943,8 @@
 
 	if (error_message != NULL || url_text == NULL || strcmp(url_text, "") == 0)
 	{
-		tmp = g_strdup_printf("<b>%s</b>", _("Error retrieving profile"));
-		purple_notify_user_info_add_pair(user_info, NULL, tmp);
-		g_free(tmp);
+		purple_notify_user_info_add_pair(user_info,
+				_("Error retrieving profile"), NULL);
 
 		purple_notify_userinfo(info_data->gc, info_data->name, user_info, NULL, NULL);
 		purple_notify_user_info_destroy(user_info);
@@ -2260,21 +2285,24 @@
 		char *p = strstr(url_buffer, "<form id=\"profile_form\" name=\"profile_form\" action=\"http&#58;&#47;&#47;spaces.live.com&#47;profile.aspx&#63;cid&#61;0\"");
 		PurpleBuddy *b = purple_find_buddy
 				(purple_connection_get_account(info_data->gc), info_data->name);
-		purple_notify_user_info_add_pair(user_info, _("Error retrieving profile"),
-									   ((p && b) ? _("The user has not created a public profile.") :
-										(p ? _("MSN reported not being able to find the user's profile. "
-											   "This either means that the user does not exist, "
-											   "or that the user exists "
-											   "but has not created a public profile.") :
-										 _("Could not find "	/* This should never happen */
-										   "any information in the user's profile. "
-										   "The user most likely does not exist."))));
+		purple_notify_user_info_add_pair(user_info,
+				_("Error retrieving profile"), NULL);
+		purple_notify_user_info_add_pair(user_info, NULL,
+				((p && b) ? _("The user has not created a public profile.") :
+					(p ? _("MSN reported not being able to find the user's profile. "
+							"This either means that the user does not exist, "
+							"or that the user exists "
+							"but has not created a public profile.") :
+						_("Could not find "	/* This should never happen */
+							"any information in the user's profile. "
+							"The user most likely does not exist."))));
 	}
 
 	/* put a link to the actual profile URL */
-	tmp = g_strdup_printf("<a href=\"%s%s\">%s%s</a>",
-					PROFILE_URL, info_data->name, PROFILE_URL, info_data->name);
-	purple_notify_user_info_add_pair(user_info, _("Profile URL"), tmp);
+	purple_notify_user_info_add_section_break(user_info);
+	tmp = g_strdup_printf("<a href=\"%s%s\">%s</a>",
+			PROFILE_URL, info_data->name, _("View web profile"));
+	purple_notify_user_info_add_pair(user_info, NULL, tmp);
 	g_free(tmp);
 
 #if PHOTO_SUPPORT
--- a/libpurple/protocols/msn/notification.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/msn/notification.c	Thu Dec 11 06:24:14 2008 +0000
@@ -849,10 +849,35 @@
 fqy_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload,
 			 size_t len)
 {
-	purple_debug_info("msn", "FQY payload:\n%s\n", payload);
-	g_return_if_fail(cmdproc->session != NULL);
-/*	msn_notification_post_adl(cmdproc, payload, len); */
-/*	msn_get_address_book(cmdproc->session, MSN_AB_SAVE_CONTACT, NULL, NULL); */
+	MsnUserList *userlist;
+	xmlnode *ml, *d, *c;
+	const char *domain;
+	const char *local;
+	const char *type;
+	char *passport;
+	MsnNetwork network = MSN_NETWORK_PASSPORT;
+
+	userlist = cmdproc->session->userlist;
+
+	/* FQY response:
+	    <ml><d n="domain.com"><c n="local-node" t="network" /></d></ml> */
+	ml = xmlnode_from_str(payload, len);
+	d = xmlnode_get_child(ml, "d");
+	c = xmlnode_get_child(d, "c");
+	domain = xmlnode_get_attrib(d, "n");
+	local = xmlnode_get_attrib(c, "n");
+	type = xmlnode_get_attrib(c, "t");
+
+	passport = g_strdup_printf("%s@%s", local, domain);
+
+	if (type != NULL)
+		network = (MsnNetwork)strtoul(type, NULL, 10);
+	purple_debug_info("msn", "FQY response says %s is from network %d\n",
+	                  passport, network);
+	msn_userlist_add_pending_buddy(userlist, passport, network);
+
+	g_free(passport);
+	xmlnode_free(ml);
 }
 
 static void
@@ -1578,7 +1603,7 @@
 	MsnUser *user;
 	const char *passport;
 	char *psm_str, *str;
-	CurrentMedia media = {NULL, NULL, NULL};
+	CurrentMedia media = {CURRENT_MEDIA_UNKNOWN, NULL, NULL, NULL};
 
 	session = cmdproc->session;
 	account = session->account;
--- a/libpurple/protocols/msn/state.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/msn/state.c	Thu Dec 11 06:24:14 2008 +0000
@@ -100,14 +100,15 @@
 	cmedia_array = g_strsplit(cmedia, "\\0", 0);
 
 	/*
-	 * 0: Media Player
-	 * 1: 'Music'
+	 * 0: Application
+	 * 1: 'Music'/'Games'/'Office'
 	 * 2: '1' if enabled, '0' if not
 	 * 3: Format (eg. {0} by {1})
 	 * 4: Title
-	 * 5: Artist
-	 * 6: Album
-	 * 7: ?
+	 * If 'Music':
+	 *  5: Artist
+	 *  6: Album
+	 *  7: ?
 	 */
 #if GLIB_CHECK_VERSION(2,6,0)
 	strings  = g_strv_length(cmedia_array);
@@ -118,6 +119,15 @@
 	if (strings >= 4 && !strcmp(cmedia_array[2], "1")) {
 		parsed = TRUE;
 
+		if (!strcmp(cmedia_array[1], "Music"))
+			media->type = CURRENT_MEDIA_MUSIC;
+		else if (!strcmp(cmedia_array[1], "Games"))
+			media->type = CURRENT_MEDIA_GAMES;
+		else if (!strcmp(cmedia_array[1], "Office"))
+			media->type = CURRENT_MEDIA_OFFICE;
+		else
+			media->type = CURRENT_MEDIA_UNKNOWN;
+
 		g_free(media->title);
 		if (strings == 4) {
 			media->title = g_strdup(cmedia_array[3]);
@@ -199,21 +209,33 @@
 static char *
 create_media_string(PurplePresence *presence)
 {
-	const char *artist, *title, *album;
+	const char *title, *game, *office;
 	char *ret;
 	PurpleStatus *status = purple_presence_get_status(presence, "tune");
 	if (!status || !purple_status_is_active(status))
-		return g_strdup_printf("WMP\\0Music\\00\\0{0} - {1}\\0\\0\\0\\0\\0");
+		return g_strdup_printf("\\0Music\\00\\0\\0");
 
-	artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST);
 	title = purple_status_get_attr_string(status, PURPLE_TUNE_TITLE);
-	album = purple_status_get_attr_string(status, PURPLE_TUNE_ALBUM);
+	game = purple_status_get_attr_string(status, "game");
+	office = purple_status_get_attr_string(status, "office");
 
-	ret = g_strdup_printf("WMP\\0Music\\0%c\\0{0} - {1}\\0%s\\0%s\\0%s\\0\\0",
-			(title && *title) ? '1' : '0',
-			title ? title : "",
-			artist ? artist : "",
-			album ? album : "");
+	if (title && *title) {
+		const char *artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST);
+		const char *album = purple_status_get_attr_string(status, PURPLE_TUNE_ALBUM);
+		ret = g_strdup_printf("WMP\\0Music\\01\\0{0}%s%s\\0%s\\0%s\\0%s\\0",
+		                      artist ? " - {1}" : "",
+		                      album ? " ({2})" : "",
+		                      title,
+		                      artist ? artist : "",
+		                      album ? album : "");
+	}
+	else if (game && *game)
+		ret = g_strdup_printf("\\0Games\\01\\0Playing {0}\\0%s\\0", game);
+	else if (office && *office)
+		ret = g_strdup_printf("\\0Office\\01\\0Editing {0}\\0%s\\0", office);
+	else
+		ret = g_strdup_printf("\\0Music\\00\\0\\0");
+
 	return ret;
 }
 
--- a/libpurple/protocols/msn/user.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/msn/user.c	Thu Dec 11 06:24:14 2008 +0000
@@ -106,12 +106,25 @@
 		purple_prpl_got_user_status_deactive(account, user->passport, "mobile");
 	}
 
-	if (!offline && user->media.title) {
-		purple_prpl_got_user_status(account, user->passport, "tune",
-				PURPLE_TUNE_ARTIST, user->media.artist,
-				PURPLE_TUNE_ALBUM, user->media.album,
-				PURPLE_TUNE_TITLE, user->media.title,
-				NULL);
+	if (!offline && user->media.type != CURRENT_MEDIA_UNKNOWN) {
+		if (user->media.type == CURRENT_MEDIA_MUSIC) {
+			purple_prpl_got_user_status(account, user->passport, "tune",
+			                            PURPLE_TUNE_ARTIST, user->media.artist,
+			                            PURPLE_TUNE_ALBUM, user->media.album,
+			                            PURPLE_TUNE_TITLE, user->media.title,
+			                            NULL);
+		} else if (user->media.type == CURRENT_MEDIA_GAMES) {
+			purple_prpl_got_user_status(account, user->passport, "tune",
+			                            "game", user->media.title,
+			                            NULL);
+		} else if (user->media.type == CURRENT_MEDIA_OFFICE) {
+			purple_prpl_got_user_status(account, user->passport, "tune",
+			                            "office", user->media.title,
+			                            NULL);
+		} else {
+			purple_debug_warning("msn", "Got CurrentMedia with unknown type %d.\n",
+			                     user->media.type);
+		}
 	} else {
 		purple_prpl_got_user_status_deactive(account, user->passport, "tune");
 	}
@@ -191,6 +204,7 @@
 	g_free(user->media.album);
 	g_free(user->media.artist);
 
+	user->media.type   = media ? media->type : CURRENT_MEDIA_UNKNOWN;
 	user->media.title  = media ? g_strdup(media->title) : NULL;
 	user->media.artist = media ? g_strdup(media->artist) : NULL;
 	user->media.album  = media ? g_strdup(media->album) : NULL;
@@ -326,6 +340,20 @@
 }
 
 void
+msn_user_set_pending_group(MsnUser *user, const char *group)
+{
+	user->pending_group = g_strdup(group);
+}
+
+char *
+msn_user_remove_pending_group(MsnUser *user)
+{
+	char *group = user->pending_group;
+	user->pending_group = NULL;
+	return group;
+}
+
+void
 msn_user_set_home_phone(MsnUser *user, const char *number)
 {
 	g_return_if_fail(user != NULL);
--- a/libpurple/protocols/msn/user.h	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/msn/user.h	Thu Dec 11 06:24:14 2008 +0000
@@ -45,11 +45,20 @@
 /**
  * Current media.
  */
+typedef enum
+{
+	CURRENT_MEDIA_UNKNOWN,
+	CURRENT_MEDIA_MUSIC,
+	CURRENT_MEDIA_GAMES,
+	CURRENT_MEDIA_OFFICE
+} CurrentMediaType;
+
 typedef struct _CurrentMedia
 {
+	CurrentMediaType type;     /**< Type.   */
+	char *title;    /**< Title.  */
 	char *artist;   /**< Artist. */
 	char *album;    /**< Album.  */
-	char *title;    /**< Title.  */
 } CurrentMedia;
 
 /**
@@ -82,6 +91,7 @@
 	gboolean mobile;        /**< Signed up with MSN Mobile.     */
 
 	GList *group_ids;       /**< The group IDs.                 */
+	char *pending_group;    /**< A pending group to add.        */
 
 	MsnObject *msnobj;      /**< The user's MSN Object.         */
 
@@ -204,6 +214,23 @@
 void msn_user_remove_group_id(MsnUser *user, const char * id);
 
 /**
+ * Sets the pending group for a user.
+ *
+ * @param user  The user.
+ * @param group The group name.
+ */
+void msn_user_set_pending_group(MsnUser *user, const char *group);
+
+/**
+ * Removes the pending group from a user.
+ *
+ * @param user The user.
+ *
+ * @return Returns the pending group name.
+ */
+char *msn_user_remove_pending_group(MsnUser *user);
+
+/**
  * Sets the home phone number for a user.
  *
  * @param user   The user.
--- a/libpurple/protocols/msn/userlist.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/msn/userlist.c	Thu Dec 11 06:24:14 2008 +0000
@@ -184,10 +184,6 @@
 		{
 			msn_user_add_group_id(user, group_id);
 		}
-		else
-		{
-			/* session->sync->fl_users_count++; */
-		}
 	}
 	else if (list_id == MSN_LIST_AL)
 	{
@@ -253,10 +249,6 @@
 			msn_user_remove_group_id(user, group_id);
 			return;
 		}
-		else
-		{
-			/* session->sync->fl_users_count--; */
-		}
 	}
 	else if (list_id == MSN_LIST_AL)
 	{
@@ -756,6 +748,62 @@
 	msn_add_contact_to_group(userlist->session, state, who, group_id);
 }
 
+/*
+ * Save a buddy address/group until we get back response from FQY
+ */
+void
+msn_userlist_save_pending_buddy(MsnUserList *userlist,
+                               const char *who,
+                               const char *group_name)
+{
+	MsnUser *user;
+
+	g_return_if_fail(userlist != NULL);
+
+	user = msn_user_new(userlist, who, NULL);
+	msn_user_set_pending_group(user, group_name);
+	msn_user_set_network(user, MSN_NETWORK_UNKNOWN);
+	userlist->pending = g_list_prepend(userlist->pending, user);
+}
+
+/*
+ * Actually adds a buddy once we have the response from FQY
+ */
+void
+msn_userlist_add_pending_buddy(MsnUserList *userlist,
+                               const char *who,
+                               /*MsnNetwork*/ int network)
+{
+	MsnUser *user = NULL;
+	GList *l;
+	char *group;
+
+	for (l = userlist->pending; l != NULL; l = l->next)
+	{
+		user = (MsnUser *)l->data;
+
+		if (!g_strcasecmp(who, user->passport)) {
+			userlist->pending = g_list_delete_link(userlist->pending, l);
+			break;
+		}
+	}
+
+	if (user == NULL) {
+		purple_debug_error("msn", "Attempting to add a pending user that does not exist.\n");
+		return;
+	}
+
+	/* Bit of a hack, but by adding to userlist now, the rest of the code
+	 * will know what network to use.
+	 */
+	msn_user_set_network(user, network);
+	msn_userlist_add_user(userlist, user);
+
+	group = msn_user_remove_pending_group(user);
+	msn_userlist_add_buddy(userlist, who, group);
+	g_free(group);
+}
+
 void
 msn_userlist_add_buddy_to_list(MsnUserList *userlist, const char *who,
 							MsnListId list_id)
--- a/libpurple/protocols/msn/userlist.h	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/msn/userlist.h	Thu Dec 11 06:24:14 2008 +0000
@@ -47,13 +47,12 @@
 
 	GList *users; /* Contains MsnUsers */
 	GList *groups; /* Contains MsnGroups */
+	GList *pending; /* MsnUsers pending addition (waiting for FQY response) */
 
 	GQueue *buddy_icon_requests;
 	int buddy_icon_window;
 	guint buddy_icon_request_timer;
 
-	int fl_users_count;
-
 };
 
 gboolean msn_userlist_user_is_in_group(MsnUser *user, const char * group_id);
@@ -93,6 +92,12 @@
 void msn_userlist_rem_buddy(MsnUserList *userlist, const char *who);
 void msn_userlist_add_buddy(MsnUserList *userlist,
 			    const char *who, const char *group_name);
+void msn_userlist_save_pending_buddy(MsnUserList *userlist,
+                                     const char *who,
+                                     const char *group_name);
+void msn_userlist_add_pending_buddy(MsnUserList *userlist,
+                                    const char *who,
+                                    /*MsnNetwork*/ int network);
 void msn_userlist_move_buddy(MsnUserList *userlist, const char *who,
 						    const char *old_group_name,
 						    const char *new_group_name);
--- a/libpurple/protocols/myspace/user.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/myspace/user.c	Thu Dec 11 06:24:14 2008 +0000
@@ -107,17 +107,6 @@
 
 	uid = purple_blist_node_get_int(&user->buddy->node, "UserID");
 
-	if (full) {
-		/* TODO: link to username, if available */
-		if (uid) {
-			char *profile = g_strdup_printf("<a href=\"http://myspace.com/%d\">http://myspace.com/%d</a>",
-											uid, uid);
-			purple_notify_user_info_add_pair(user_info, _("Profile"), profile);
-			g_free(profile);
-		}
-	}
-
-
 	/* a/s/l...the vitals */
 	if (user->age) {
 		char age[16];
@@ -180,6 +169,16 @@
 			purple_notify_user_info_add_pair(user_info, _("Client Version"), client);
 		g_free(client);
 	}
+
+	if (full && uid) {
+		/* TODO: link to username, if available */
+		char *profile;
+		purple_notify_user_info_add_section_break(user_info);
+		profile = g_strdup_printf("<a href=\"http://myspace.com/%d\">%s</a>",
+				uid, _("View web profile"));
+		purple_notify_user_info_add_pair(user_info, NULL, profile);
+		g_free(profile);
+	}
 }
 
 /** Set the currently playing song artist and or title.
--- a/libpurple/protocols/null/nullprpl.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/null/nullprpl.c	Thu Dec 11 06:24:14 2008 +0000
@@ -160,8 +160,8 @@
 
 static void discover_status(PurpleConnection *from, PurpleConnection *to,
                             gpointer userdata) {
-  char *from_username = from->account->username;
-  char *to_username = to->account->username;
+  const char *from_username = from->account->username;
+  const char *to_username = to->account->username;
 
   if (purple_find_buddy(from->account, to_username)) {
     PurpleStatus *status = purple_account_get_active_status(to->account);
@@ -262,7 +262,7 @@
 
   } else {
     purple_debug_info("nullprpl", "...but %s is not logged in\n", buddy->name);
-    return "Not logged in";
+    return g_strdup("Not logged in");
   }
 }
 
@@ -275,9 +275,10 @@
     /* they're logged in */
     PurplePresence *presence = purple_buddy_get_presence(buddy);
     PurpleStatus *status = purple_presence_get_active_status(presence);
-    const char *msg = nullprpl_status_text(buddy);
+    char *msg = nullprpl_status_text(buddy);
     purple_notify_user_info_add_pair(info, purple_status_get_name(status),
                                      msg);
+    g_free(msg);
 
     if (full) {
       const char *user_info = purple_account_get_user_info(gc->account);
@@ -289,7 +290,7 @@
     /* they're not logged in */
     purple_notify_user_info_add_pair(info, _("User info"), _("not logged in"));
   }
-    
+
   purple_debug_info("nullprpl", "showing %s tooltip for %s\n",
                     (full) ? "full" : "short", buddy->name);
 }
@@ -307,21 +308,21 @@
                                 NULL_STATUS_ONLINE, TRUE);
   purple_status_type_add_attr(type, "message", _("Online"),
                               purple_value_new(PURPLE_TYPE_STRING));
-  types = g_list_append(types, type);
+  types = g_list_prepend(types, type);
 
   type = purple_status_type_new(PURPLE_STATUS_AWAY, NULL_STATUS_AWAY,
                                 NULL_STATUS_AWAY, TRUE);
   purple_status_type_add_attr(type, "message", _("Away"),
                               purple_value_new(PURPLE_TYPE_STRING));
-  types = g_list_append(types, type);
+  types = g_list_prepend(types, type);
   
   type = purple_status_type_new(PURPLE_STATUS_OFFLINE, NULL_STATUS_OFFLINE,
                                 NULL_STATUS_OFFLINE, TRUE);
   purple_status_type_add_attr(type, "message", _("Offline"),
                               purple_value_new(PURPLE_TYPE_STRING));
-  types = g_list_append(types, type);
+  types = g_list_prepend(types, type);
 
-  return types;
+  return g_list_reverse(types);
 }
 
 static void blist_example_menu_item(PurpleBlistNode *node, gpointer userdata) {
@@ -355,7 +356,7 @@
   purple_debug_info("nullprpl", "returning chat setting 'room'\n");
 
   pce = g_new0(struct proto_chat_entry, 1);
-  pce->label = _(_("Chat _room"));
+  pce->label = _("Chat _room");
   pce->identifier = "room";
   pce->required = TRUE;
 
@@ -477,7 +478,7 @@
                     gc->account->username, info);
 }
 
-static char *typing_state_to_string(PurpleTypingState typing) {
+static const char *typing_state_to_string(PurpleTypingState typing) {
   switch (typing) {
   case PURPLE_NOT_TYPING:  return "is not typing";
   case PURPLE_TYPING:      return "is typing";
@@ -488,8 +489,8 @@
 
 static void notify_typing(PurpleConnection *from, PurpleConnection *to,
                           gpointer typing) {
-  char *from_username = from->account->username;
-  char *action = typing_state_to_string((PurpleTypingState)typing);
+  const char *from_username = from->account->username;
+  const char *action = typing_state_to_string((PurpleTypingState)typing);
   purple_debug_info("nullprpl", "notifying %s that %s %s\n",
                     to->account->username, from_username, action);
 
@@ -561,7 +562,7 @@
 static void nullprpl_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
                                PurpleGroup *group)
 {
-  char *username = gc->account->username;
+  const char *username = gc->account->username;
   PurpleConnection *buddy_gc = get_nullprpl_gc(buddy->name);
 
   purple_debug_info("nullprpl", "adding %s to %s's buddy list\n", buddy->name,
@@ -679,8 +680,8 @@
 }
 
 static void nullprpl_join_chat(PurpleConnection *gc, GHashTable *components) {
-  char *username = gc->account->username;
-  char *room = g_hash_table_lookup(components, "room");
+  const char *username = gc->account->username;
+  const char *room = g_hash_table_lookup(components, "room");
   int chat_id = g_str_hash(room);
   purple_debug_info("nullprpl", "%s is joining chat room %s\n", username, room);
 
@@ -690,20 +691,20 @@
     /* tell everyone that we joined, and add them if they're already there */
     foreach_gc_in_chat(joined_chat, gc, chat_id, NULL);
   } else {
+    char *tmp = g_strdup_printf(_("%s is already in chat room %s."),
+                                username,
+                                room);
     purple_debug_info("nullprpl", "%s is already in chat room %s\n", username,
                       room);
-    purple_notify_info(gc,
-                       _("Join chat"),
-                       _("Join chat"),
-                       g_strdup_printf("%s is already in chat room %s.",
-                                       username, room));
+    purple_notify_info(gc, _("Join chat"), _("Join chat"), tmp);
+    g_free(tmp);
   }
 }
 
 static void nullprpl_reject_chat(PurpleConnection *gc, GHashTable *components) {
-  char *invited_by = g_hash_table_lookup(components, "invited_by");
-  char *room = g_hash_table_lookup(components, "room");
-  char *username = gc->account->username;
+  const char *invited_by = g_hash_table_lookup(components, "invited_by");
+  const char *room = g_hash_table_lookup(components, "room");
+  const char *username = gc->account->username;
   PurpleConnection *invited_by_gc = get_nullprpl_gc(invited_by);
   char *message = g_strdup_printf(
     "%s %s %s.",
@@ -719,19 +720,20 @@
                      _("Chat invitation rejected"),
                      _("Chat invitation rejected"),
                      message);
+  g_free(message);
 }
 
 static char *nullprpl_get_chat_name(GHashTable *components) {
-  char *room = g_hash_table_lookup(components, "room");
+  const char *room = g_hash_table_lookup(components, "room");
   purple_debug_info("nullprpl", "reporting chat room name '%s'\n", room);
-  return room;
+  return g_strdup(room);
 }
 
 static void nullprpl_chat_invite(PurpleConnection *gc, int id,
                                  const char *message, const char *who) {
-  char *username = gc->account->username;
+  const char *username = gc->account->username;
   PurpleConversation *conv = purple_find_chat(gc, id);
-  char *room = conv->name;
+  const char *room = conv->name;
   PurpleAccount *to_acct = purple_accounts_find(who, NULLPRPL_ID);
 
   purple_debug_info("nullprpl", "%s is inviting %s to join chat room %s\n",
@@ -740,18 +742,16 @@
   if (to_acct) {
     PurpleConversation *to_conv = purple_find_chat(to_acct->gc, id);
     if (to_conv) {
+      char *tmp = g_strdup_printf("%s is already in chat room %s.", who, room);
       purple_debug_info("nullprpl",
                         "%s is already in chat room %s; "
                         "ignoring invitation from %s\n",
                         who, room, username);
-      purple_notify_info(gc,
-                         _("Chat invitation"),
-                         _("Chat invitation"),
-                         g_strdup_printf("%s is already in chat room %s.",
-                                         who, room));
+      purple_notify_info(gc, _("Chat invitation"), _("Chat invitation"), tmp);
+      g_free(tmp);
     } else {
       GHashTable *components;
-      components = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, free);
+      components = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
       g_hash_table_replace(components, "room", g_strdup(room));
       g_hash_table_replace(components, "invited_by", g_strdup(username));
       serv_got_chat_invite(to_acct->gc, room, username, message, components);
@@ -833,7 +833,7 @@
 
 static void nullprpl_chat_whisper(PurpleConnection *gc, int id, const char *who,
                                   const char *message) {
-  char *username = gc->account->username;
+  const char *username = gc->account->username;
   PurpleConversation *conv = purple_find_chat(gc, id);
   purple_debug_info("nullprpl",
                     "%s receives whisper from %s in chat room %s: %s\n",
@@ -858,7 +858,7 @@
 
 static int nullprpl_chat_send(PurpleConnection *gc, int id, const char *message,
                               PurpleMessageFlags flags) {
-  char *username = gc->account->username;
+  const char *username = gc->account->username;
   PurpleConversation *conv = purple_find_chat(gc, id);
 
   if (conv) {
@@ -981,7 +981,7 @@
 }
 
 static PurpleRoomlist *nullprpl_roomlist_get_list(PurpleConnection *gc) {
-  char *username = gc->account->username;
+  const char *username = gc->account->username;
   PurpleRoomlist *roomlist = purple_roomlist_new(gc->account);
   GList *fields = NULL;
   PurpleRoomlistField *field;
@@ -1005,14 +1005,17 @@
   for (chats  = purple_get_chats(); chats; chats = g_list_next(chats)) {
     PurpleConversation *conv = (PurpleConversation *)chats->data;
     PurpleRoomlistRoom *room;
-    char *name = conv->name;
+    const char *name = conv->name;
     int id = purple_conversation_get_chat_data(conv)->id;
 
     /* have we already added this room? */
     if (g_list_find_custom(seen_ids, name, (GCompareFunc)strcmp))
       continue;                                /* yes! try the next one. */
 
-    seen_ids = g_list_append(seen_ids, name);  /* no, it's new. */
+    /* This cast is OK because this list is only staying around for the life
+     * of this function and none of the conversations are being deleted
+	 * in that timespan. */
+    seen_ids = g_list_prepend(seen_ids, (char *)name); /* no, it's new. */
     purple_debug_info("nullprpl", "%s (%d), ", name, id);
 
     room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, name, NULL);
@@ -1021,6 +1024,7 @@
     purple_roomlist_room_add(roomlist, room);
   }
 
+  g_list_free(seen_ids);
   purple_timeout_add(1 /* ms */, nullprpl_finish_get_roomlist, roomlist);
   return roomlist;
 }
--- a/libpurple/protocols/oscar/bstream.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/oscar/bstream.c	Thu Dec 11 06:24:14 2008 +0000
@@ -302,3 +302,12 @@
 
 	return len;
 }
+
+int byte_stream_putuid(ByteStream *bs, OscarData *od)
+{
+	PurpleAccount *account;
+
+	account = purple_connection_get_account(od->gc);
+
+	return byte_stream_putle32(bs, atoi(purple_account_get_username(account)));
+}
--- a/libpurple/protocols/oscar/family_auth.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/oscar/family_auth.c	Thu Dec 11 06:24:14 2008 +0000
@@ -295,10 +295,9 @@
 	/*
 	 * No matter what, we should have a screen name.
 	 */
-	memset(od->sn, 0, sizeof(od->sn));
 	if (aim_tlv_gettlv(tlvlist, 0x0001, 1)) {
 		info->sn = aim_tlv_getstr(tlvlist, 0x0001, 1);
-		strncpy(od->sn, info->sn, sizeof(od->sn));
+		purple_connection_set_display_name(od->gc, info->sn);
 	}
 
 	/*
--- a/libpurple/protocols/oscar/family_icbm.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/oscar/family_icbm.c	Thu Dec 11 06:24:14 2008 +0000
@@ -1214,7 +1214,7 @@
 	/*
 	 * Your UIN
 	 */
-	byte_stream_putle32(&bs, atoi(od->sn));
+	byte_stream_putuid(&bs, od);
 
 	/*
 	 * TLV t(type) l(strlen(message)+1) v(message+NULL)
--- a/libpurple/protocols/oscar/family_icq.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/oscar/family_icq.c	Thu Dec 11 06:24:14 2008 +0000
@@ -36,7 +36,7 @@
 	if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICQ)))
 		return -EINVAL;
 
-	purple_debug_info("oscar", "Requesting offline messages from %s", od->sn);
+	purple_debug_info("oscar", "Requesting offline messages\n");
 
 	bslen = 2 + 4 + 2 + 2;
 
@@ -49,7 +49,7 @@
 	byte_stream_put16(&bs, bslen);
 
 	byte_stream_putle16(&bs, bslen - 2);
-	byte_stream_putle32(&bs, atoi(od->sn));
+	byte_stream_putuid(&bs, od);
 	byte_stream_putle16(&bs, 0x003c); /* I command thee. */
 	byte_stream_putle16(&bs, snacid); /* eh. */
 
@@ -70,7 +70,7 @@
 	if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICQ)))
 		return -EINVAL;
 
-	purple_debug_info("oscar", "Acknowledged receipt of offline messages from %s", od->sn);
+	purple_debug_info("oscar", "Acknowledged receipt of offline messages\n");
 
 	bslen = 2 + 4 + 2 + 2;
 
@@ -83,7 +83,7 @@
 	byte_stream_put16(&bs, bslen);
 
 	byte_stream_putle16(&bs, bslen - 2);
-	byte_stream_putle32(&bs, atoi(od->sn));
+	byte_stream_putuid(&bs, od);
 	byte_stream_putle16(&bs, 0x003e); /* I command thee. */
 	byte_stream_putle16(&bs, snacid); /* eh. */
 
@@ -117,7 +117,7 @@
 	byte_stream_put16(&bs, bslen);
 
 	byte_stream_putle16(&bs, bslen - 2);
-	byte_stream_putle32(&bs, atoi(od->sn));
+	byte_stream_putuid(&bs, od);
 	byte_stream_putle16(&bs, 0x07d0); /* I command thee. */
 	byte_stream_putle16(&bs, snacid); /* eh. */
 	byte_stream_putle16(&bs, 0x0c3a); /* shrug. */
@@ -172,7 +172,7 @@
 	byte_stream_put16(&bs, bslen);
 
 	byte_stream_putle16(&bs, bslen - 2);
-	byte_stream_putle32(&bs, atoi(od->sn));
+	byte_stream_putuid(&bs, od);
 	byte_stream_putle16(&bs, 0x07d0); /* I command thee. */
 	byte_stream_putle16(&bs, snacid); /* eh. */
 	byte_stream_putle16(&bs, 0x042e); /* shrug. */
@@ -212,7 +212,7 @@
 	byte_stream_put16(&bs, bslen);
 
 	byte_stream_putle16(&bs, bslen - 2);
-	byte_stream_putle32(&bs, atoi(od->sn));
+	byte_stream_putuid(&bs, od);
 	byte_stream_putle16(&bs, 0x07d0); /* I command thee. */
 	byte_stream_putle16(&bs, snacid); /* eh. */
 	byte_stream_putle16(&bs, 0x04b2); /* shrug. */
@@ -259,7 +259,7 @@
 	byte_stream_put16(&bs, bslen);
 
 	byte_stream_putle16(&bs, bslen - 2);
-	byte_stream_putle32(&bs, atoi(od->sn));
+	byte_stream_putuid(&bs, od);
 	byte_stream_putle16(&bs, 0x07d0); /* I command thee. */
 	byte_stream_putle16(&bs, snacid); /* eh. */
 	byte_stream_putle16(&bs, 0x04ba); /* shrug. */
@@ -303,7 +303,7 @@
 	byte_stream_put16(&bs, bslen);
 
 	byte_stream_putle16(&bs, bslen - 2);
-	byte_stream_putle32(&bs, atoi(od->sn));
+	byte_stream_putuid(&bs, od);
 	byte_stream_putle16(&bs, 0x07d0); /* I command thee. */
 	byte_stream_putle16(&bs, snacid); /* eh. */
 	byte_stream_putle16(&bs, 0x051f); /* shrug. */
@@ -341,7 +341,7 @@
 	byte_stream_put16(&bs, bslen);
 
 	byte_stream_putle16(&bs, bslen - 2);
-	byte_stream_putle32(&bs, atoi(od->sn));
+	byte_stream_putuid(&bs, od);
 	byte_stream_putle16(&bs, 0x07d0); /* I command thee. */
 	byte_stream_putle16(&bs, snacid); /* eh. */
 	byte_stream_putle16(&bs, 0x0998); /* shrug. */
@@ -377,11 +377,12 @@
 int aim_icq_sendsms(OscarData *od, const char *name, const char *msg, const char *alias)
 {
 	FlapConnection *conn;
+	PurpleAccount *account;
 	ByteStream bs;
 	aim_snacid_t snacid;
 	int bslen, xmllen;
 	char *xml;
-	const char *timestr;
+	const char *timestr, *username;
 	time_t t;
 	struct tm *tm;
 	gchar *stripped;
@@ -392,6 +393,9 @@
 	if (!name || !msg || !alias)
 		return -EINVAL;
 
+	account = purple_connection_get_account(od->gc);
+	username = purple_account_get_username(account);
+
 	time(&t);
 	tm = gmtime(&t);
 	timestr = purple_utf8_strftime("%a, %d %b %Y %T %Z", tm);
@@ -399,7 +403,7 @@
 	stripped = purple_markup_strip_html(msg);
 
 	/* The length of xml included the null terminating character */
-	xmllen = 209 + strlen(name) + strlen(stripped) + strlen(od->sn) + strlen(alias) + strlen(timestr) + 1;
+	xmllen = 209 + strlen(name) + strlen(stripped) + strlen(username) + strlen(alias) + strlen(timestr) + 1;
 
 	xml = g_new(char, xmllen);
 	snprintf(xml, xmllen, "<icq_sms_message>"
@@ -411,7 +415,7 @@
 		"<delivery_receipt>Yes</delivery_receipt>"
 		"<time>%s</time>"
 		"</icq_sms_message>",
-		name, stripped, od->sn, alias, timestr);
+		name, stripped, username, alias, timestr);
 
 	bslen = 36 + xmllen;
 
@@ -424,7 +428,7 @@
 	byte_stream_put16(&bs, bslen);
 
 	byte_stream_putle16(&bs, bslen - 2);
-	byte_stream_putle32(&bs, atoi(od->sn));
+	byte_stream_putuid(&bs, od);
 	byte_stream_putle16(&bs, 0x07d0); /* I command thee. */
 	byte_stream_putle16(&bs, snacid); /* eh. */
 
@@ -481,7 +485,7 @@
 	byte_stream_put16(&bs, bslen);
 
 	byte_stream_putle16(&bs, bslen - 2);
-	byte_stream_putle32(&bs, atoi(od->sn));
+	byte_stream_putuid(&bs, od);
 	byte_stream_putle16(&bs, 0x07d0); /* I command thee. */
 	byte_stream_putle16(&bs, snacid); /* eh. */
 	byte_stream_putle16(&bs, 0x0fa0); /* shrug. */
--- a/libpurple/protocols/oscar/oscar.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Thu Dec 11 06:24:14 2008 +0000
@@ -3187,6 +3187,12 @@
 		}
 	}
 
+	purple_notify_user_info_add_section_break(user_info);
+	tmp = g_strdup_printf("<a href=\"http://profiles.aim.com/%s\">%s</a>",
+			purple_normalize(account, userinfo->sn), _("View web profile"));
+	purple_notify_user_info_add_pair(user_info, NULL, tmp);
+	g_free(tmp);
+
 	purple_notify_userinfo(gc, userinfo->sn, user_info, NULL, NULL);
 	purple_notify_user_info_destroy(user_info);
 
--- a/libpurple/protocols/oscar/oscar.h	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Thu Dec 11 06:24:14 2008 +0000
@@ -488,16 +488,8 @@
 		guint maxawaymsglen; /* max size (bytes) of posted away message */
 	} rights;
 
-	/* ---- Client Accessible ------------------------ */
-
-	/* Our screen name. */
-	/* TODO: Get rid of this and use purple_account_get_username() everywhere? */
-	char sn[MAXSNLEN+1];
-
 	PurpleConnection *gc;
 
-	/* ---- Internal Use Only ------------------------ */
-
 	void *modlistv;
 
 	/*
@@ -1587,6 +1579,7 @@
 int byte_stream_putraw(ByteStream *bs, const guint8 *v, int len);
 int byte_stream_putstr(ByteStream *bs, const char *str);
 int byte_stream_putbs(ByteStream *bs, ByteStream *srcbs, int len);
+int byte_stream_putuid(ByteStream *bs, OscarData *od);
 int byte_stream_putcaps(ByteStream *bs, guint32 caps);
 
 /*
--- a/libpurple/protocols/qq/ChangeLog	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/ChangeLog	Thu Dec 11 06:24:14 2008 +0000
@@ -1,3 +1,35 @@
+2008.12.06 - flos <lonicerae(at)gmail.com>
+	* Removed version checking script in Makefiles since our developers all migrated to monotone
+	* Use our development revision as OPENQ_VERSION in qq.c
+
+2008.12.05 - flos <lonicerae(at)gmail.com>
+	* Fixed a bug after propagating
+
+2008.11.18 - ccpaging <ccpaging(at)gmail.com>
+	* Fixed: IM format suuport in IM and QUN IM
+	* Divide long IM message into segment and sending
+	* Divide long QUN IM message in to segment and sending
+	* Add some new function in im.c to put format when sending
+	* Add some new function in im.c to get format when receiving
+	* Need improvement:
+	    Merge long IM message when receiving. Need a buffer to store segments of long IM message.
+	    Send segment of long IM message one by one. Need a buffer to store segments of long IM message.
+
+2008.11.11 - ccpaging <ccpaging(at)gmail.com>
+	* Change QQ number to unsigned long
+	* Change Qun ID and Qun extend ID to unsigned long
+	* Rewrite smiley convert function, use qsort and bsearch
+	* Update smiley map according EVA and pidgin theme file
+	* Support long IM message in private and Qun
+
+2008.10.27 - ccpaging <ccpaging(at)gmail.com>
+	* Fixed a bug in group_join.c
+
+2008.10.30 - flos <lonicerae(at)gmail.com>
+	* Fixed a bug which made xgettext failed in buddy_info.c
+	* Fixed a bug in Makefile.am and Makefile.mingw
+	* Updated acknowledgement in qq.c
+
 2008.10.28 - flos <lonicerae(at)gmail.com>
 	* Updated AUTHORS
 
@@ -22,15 +54,15 @@
 
 2008.10.10 - ccpaging <ccpaging(at)gmail.com>
 	* Keep group_search.c/h for later use
-	* Update 'group' 
+	* Update 'group'
 
 2008.10.09 - ccpaging <ccpaging(at)gmail.com>
 	* 20081009-1
 
 2008.10.09 - ccpaging <ccpaging(at)gmail.com>
 	* Update 'group' protocol
-	* Functions of group_find, group_free, group_search merged into group_join and group_internal 
-	* Removed group_find.c/h, group_free.c/h, group_search.c/h 
+	* Functions of group_find, group_free, group_search merged into group_join and group_internal
+	* Removed group_find.c/h, group_free.c/h, group_search.c/h
 
 2008.10.08 - ccpaging <ccpaging(at)gmail.com>
 	* Update 'group' protocol
@@ -138,7 +170,7 @@
 		1. send next package till the previous package received
 		2. fix duplicated get_room_info and get_room_buddies commands
 
-2008.08.16 - ccpaging <ecc_hy(at)hotmail.com>
+2008.08.16 - ccpaging <ccpaging(at)gmail.com>
 	* Rename group to room. If you used pidginqq before, this may create a new room with same title, you may delete old one
 	* Replace purple_debug with purple_debug_info, purple_debug_warning, purple_debug_error
 	* Add server notice and server new, and two options to turn on/off
@@ -151,17 +183,17 @@
 2008.08.10 - csyfek <csyfek(at)gmail.com>
 	* Commit to Pidgin
 
-2008.08.07 - ccpaging <ecc_hy(at)hotmail.com>
+2008.08.07 - ccpaging <ccpaging(at)gmail.com>
 	* Support managing multi-connections according to simple.c
 
-2008.08.06 - ccpaging <ecc_hy(at)hotmail.com>
+2008.08.06 - ccpaging <ccpaging(at)gmail.com>
 	* Rename names of variables, Group, to Room
 	* Functions of group_network merged into qq_network and qq_process
 	* Canceled managing glist of group packet, add sub_cmdd and room_id  to transaction
 	* Fixed error of demo group:
 		If 'room list' and 'room infor' are not setup, response received from server will emits 'room_id = 0' packet.
 
-2008.08.04 - ccpaging <ecc_hy(at)hotmail.com>
+2008.08.04 - ccpaging <ccpaging(at)gmail.com>
 	* Use new crypt/decrypt functions
 	* Rename crypt.c/h to qq_crypt.c/h
 	* Clean code of decrypt functions
@@ -180,17 +212,17 @@
 		Fixes #1902
 		References #5112
 
-2008.08.02 - ccpaging <ecc_hy(at)hotmail.com>
+2008.08.02 - ccpaging <ccpaging(at)gmail.com>
 	* Store all keys and md5 values of qq_data in char[QQ_KEY_LENGTH]
 	* Use random value in inikey
 	* TEA header padding in crypt.c
 	* Rewrite login part of qq_process
 
-2008.07.31 - ccpaging <ecc_hy(at)hotmail.com>
+2008.07.31 - ccpaging <ccpaging(at)gmail.com>
 	* Fixed: send reply when get duplicate server command. The server may not get our reply before.
 	* Tag custom picture as text "(Broken)"
 
-2008.07.30 - ccpaging <ecc_hy(at)hotmail.com>, csyfek <csyfek(at)gmail.com>
+2008.07.30 - ccpaging <ccpaging(at)gmail.com>, csyfek <csyfek(at)gmail.com>
 	* Change some debug message
 	* Modify buddy status flag according to eva for QQ2006
 	* Modify buddy status parse and correspond to eva2
@@ -224,10 +256,10 @@
 	* Rewrite qq_proc_cmd_reply and qq_proc_cmd_server:
 		In QQ protocol, one packet reply may need a new packet send later.
 		We may call it packet trigger. The triggers always is hided in every qq_process_reply.
-		Now we try to extract those triggers and put into a single function, 
+		Now we try to extract those triggers and put into a single function,
 		and then every trigger should be obviously and easy to manage.
-	
-2008.07.12 - ccpaging <ecc_hy(at)hotmail.com>
+
+2008.07.12 - ccpaging <ccpaging(at)gmail.com>
 	* Fixed: Always lost connection. Now send keep alive packet in every 30 seconds
 	* Minor fix for debug information
 	* Filter \r\n and replace with SPCAE in group notive
@@ -240,37 +272,37 @@
 	* Add some doxygen syntax for preparing development documentation
 	* References #6199
 
-2008.06.28 - ccpaging <ecc_hy(at)hotmail.com>, moo <phpxcache(at)gmail.com>
+2008.06.28 - ccpaging <ccpaging(at)gmail.com>, moo <phpxcache(at)gmail.com>
 	* Patches from moo<phpxcache@gmail.com> and ccpaging<ccpaging@foxmail.com>.
 	* Tickets:
 	* Fixes #4956.
 	* Fixes #2998.
 
-2008.06.07 - ccpaging <ecc_hy(at)hotmail.com>, csyfek <csyfek(at)gmail.com>
+2008.06.07 - ccpaging <ccpaging(at)gmail.com>, csyfek <csyfek(at)gmail.com>
 	* Clean code and apply patches from QuLogic
 
-2008.05.19 - ccpaging <ecc_hy(at)hotmail.com>, csyfek <csyfek(at)gmail.com>
+2008.05.19 - ccpaging <ccpaging(at)gmail.com>, csyfek <csyfek(at)gmail.com>
 	* Reconnect server 5 time in 5000 ms, when connect failed
 	* Rename sendqueue.c/sendqueue.h to qq_trans.c/qq_trans.h
 	* Rewrite packet_process
 	* Rewrite qq_send_cmd
 	* Create server list, try to connect every server when failed
 
-2008.05.14 - ccpaging <ecc_hy(at)hotmail.com>
+2008.05.14 - ccpaging <ccpaging(at)gmail.com>
 	* Move function for before login packets storing to sendqueue
 	* Use transaction data structure to store before login packets
 	* Rewrite tcp_pending and packet_process in qq_network.c
 
-2008.05.09 - ccpaging <ecc_hy(at)hotmail.com>
+2008.05.09 - ccpaging <ccpaging(at)gmail.com>
 	* Remove function _create_packet_head_seq in qq_network.c
 	* Create new function encap in qq_netowork.c
 	* Clean code of qq_send_packet_request_login_token and qq_send_packet_login in login_out.c
 
-2008.05.09 - ccpaging <ecc_hy(at)hotmail.com>
+2008.05.09 - ccpaging <ccpaging(at)gmail.com>
 	* Clean code of packet_parse.c, enable PARSER_DEBUG
 	* Rewrite send_queue
 
-2008.05.08 - ccpaging <ecc_hy(at)hotmail.com>
+2008.05.08 - ccpaging <ccpaging(at)gmail.com>
 	* Rewrite qq_network
 	* Add srv resolve function when qq_login
 	* Merge function _qq_common_clean in qq_proxy.c to qq_disconnect
@@ -278,20 +310,20 @@
 	* qq_data alloc in qq_open and release in qq_close
 	* Network connect of QQ is created in qq_connect, and release in qq_disconnect
 
-2008.05.05 - ccpaging <ecc_hy(at)hotmail.com>
+2008.05.05 - ccpaging <ccpaging(at)gmail.com>
 	* Merge function _qq_common_clean in qq_proxy.c to qq_disconnect
 	* Move orignal qq_disconnect to qq_close
 	* qq_data alloc in qq_open and release in qq_close
 	* Network connect of QQ is created in qq_connect, and release in qq_disconnect
 
-2008.05.05 - ccpaging <ecc_hy(at)hotmail.com>
+2008.05.05 - ccpaging <ccpaging(at)gmail.com>
 	* Add qq_hex_dump function
 
-2008.04.25 - ccpaging <ecc_hy(at)hotmail.com>, csyfek <csyfek(at)gmail.com>
+2008.04.25 - ccpaging <ccpaging(at)gmail.com>, csyfek <csyfek(at)gmail.com>
 	* Rewrite read_packet and create_packet functions, use qq_put and qq_get functions instead
 	* New logic in accord with protocol models to handle packets, some related functions rewritten
 
-2008.03.24 - ccpaging <ecc_hy(at)hotmail.com>
+2008.03.24 - ccpaging <ccpaging(at)gmail.com>
 	* Remove qq_crypt function in crypt.c, use qq_crypt and qq_decrypt directly
 
 ** since pidgin-2.4.0 ***
--- a/libpurple/protocols/qq/Makefile.mingw	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/Makefile.mingw	Thu Dec 11 06:24:14 2008 +0000
@@ -6,6 +6,7 @@
 
 PIDGIN_TREE_TOP := ../../..
 include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
+
 TARGET = libqq
 TYPE = PLUGIN
 
--- a/libpurple/protocols/qq/buddy_info.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/buddy_info.c	Thu Dec 11 06:24:14 2008 +0000
@@ -58,7 +58,7 @@
 
 #define QQ_PUBLISH_SIZE 3
 static const gchar *publish_types[] = {
-	N_("Visible"), N_("Firend Only"), N_("Private")
+	N_("Visible"), N_("Friend Only"), N_("Private")
 };
 
 #define QQ_GENDER_SIZE 3
@@ -228,7 +228,7 @@
 	g_return_if_fail(uid != 0);
 
 	qd = (qq_data *) gc->proto_data;
-	g_snprintf(raw_data, sizeof(raw_data), "%d", uid);
+	g_snprintf(raw_data, sizeof(raw_data), "%u", uid);
 	qq_send_cmd_mess(gc, QQ_CMD_GET_BUDDY_INFO, (guint8 *) raw_data, strlen(raw_data),
 			update_class, action);
 }
@@ -457,7 +457,7 @@
 	data[data_len] = '\0';
 	if (qd->uid != atoi((gchar *) data)) {	/* return should be my uid */
 		purple_debug_info("QQ", "Failed Updating info\n");
-		qq_got_attention(gc, _("Could not change buddy information."));
+		qq_got_message(gc, _("Could not change buddy information."));
 	}
 }
 
@@ -504,6 +504,8 @@
 {
 	PurpleAccount *account = purple_connection_get_account(gc);
 	const gchar *icon_path = purple_account_get_buddy_icon_path(account);
+	gchar **segments;
+	gint index;
 
 	g_return_if_fail(icon_path != NULL);
 
@@ -512,6 +514,12 @@
 	 *  purple_imgstore_get_filename is always new file
 	 *  QQ buddy may set custom icon if level is over 16 */
 	purple_debug_info("QQ", "Change my icon to %s\n", icon_path);
+	segments = g_strsplit_set(icon_path, G_DIR_SEPARATOR_S, 0);
+	for (index = 0; segments[index] != NULL; index++) {
+		purple_debug_info("QQ", "Split to %s\n", segments[index]);
+	}
+
+	g_strfreev(segments);
 }
 
 gchar *qq_get_icon_name(gint face)
@@ -553,7 +561,7 @@
 	return icon_path;
 }
 
-static void update_buddy_icon(PurpleAccount *account, const gchar *who, gint face)
+void qq_update_buddy_icon(PurpleAccount *account, const gchar *who, gint face)
 {
 	PurpleBuddy *buddy;
 	const gchar *icon_name_prev = NULL;
@@ -564,19 +572,21 @@
 
 	g_return_if_fail(account != NULL && who != NULL);
 
-	purple_debug_info("QQ", "Update %s icon to %d\n", who, face);
+	/* purple_debug_info("QQ", "Update %s icon to %d\n", who, face); */
 
 	icon_name = qq_get_icon_name(face);
-	purple_debug_info("QQ", "icon file name is %s\n", icon_name);
+	g_return_if_fail(icon_name != NULL);
+	/* purple_debug_info("QQ", "icon file name is %s\n", icon_name); */
 
 	if ((buddy = purple_find_buddy(account, who))) {
 		icon_name_prev = purple_buddy_icons_get_checksum_for_user(buddy);
-		if (icon_name_prev != NULL) {
-			purple_debug_info("QQ", "Previous icon is %s\n", icon_name_prev);
-		}
+		/*
+		purple_debug_info("QQ", "Previous icon is %s\n",
+				icon_name_prev != NULL ? icon_name_prev : "(NULL)");
+		*/
 	}
 	if (icon_name_prev != NULL && !strcmp(icon_name, icon_name_prev)) {
-		purple_debug_info("QQ", "Icon is not changed\n");
+		/* purple_debug_info("QQ", "Icon is not changed\n"); */
 		g_free(icon_name);
 		return;
 	}
@@ -590,6 +600,8 @@
 	if (!g_file_get_contents(icon_path, &icon_file_content, &icon_file_size, NULL)) {
 		purple_debug_error("QQ", "Failed reading icon file %s\n", icon_path);
 	} else {
+		purple_debug_info("QQ", "Update %s icon to %d (%s)\n",
+				who, face, icon_path);
 		purple_buddy_icons_set_for_user(account, who,
 				icon_file_content, icon_file_size, icon_name);
 	}
@@ -610,7 +622,7 @@
 
 	qd = (qq_data *) gc->proto_data;
 
-	uid = strtol(segments[QQ_INFO_UID], NULL, 10);
+	uid = strtoul(segments[QQ_INFO_UID], NULL, 10);
 	who = uid_to_purple_name(uid);
 
 	qq_filter_str(segments[QQ_INFO_NICK]);
@@ -648,7 +660,7 @@
 	purple_blist_server_alias_buddy(buddy, bd->nickname);
 
 	/* convert face num from packet (0-299) to local face (1-100) */
-	update_buddy_icon(gc->account, who, bd->face);
+	qq_update_buddy_icon(gc->account, who, bd->face);
 
 	g_free(who);
 	g_free(alias_utf8);
@@ -786,12 +798,12 @@
 		bytes += qq_get32(&onlineTime, data + bytes);
 		bytes += qq_get16(&level, data + bytes);
 		bytes += qq_get16(&timeRemainder, data + bytes);
-		purple_debug_info("QQ_LEVEL", "%d, tmOnline: %d, level: %d, tmRemainder: %d\n",
-				uid, onlineTime, level, timeRemainder);
+		purple_debug_info("QQ", "level: %d, uid %d, tmOnline: %d, tmRemainder: %d\n",
+				level, uid, onlineTime, timeRemainder);
 
 		bd = qq_buddy_data_find(gc, uid);
 		if (bd == NULL) {
-			purple_debug_error("QQ", "Got levels of %d not in my buddy list\n", uid);
+			purple_debug_error("QQ", "Got levels of %u not in my buddy list\n", uid);
 			continue;
 		}
 
@@ -821,8 +833,8 @@
 	bytes += qq_get32(&onlineTime, data + bytes);
 	bytes += qq_get16(&level, data + bytes);
 	bytes += qq_get16(&timeRemainder, data + bytes);
-	purple_debug_info("QQ_LEVEL", "%d, tmOnline: %d, level: %d, tmRemainder: %d\n",
-			uid, onlineTime, level, timeRemainder);
+	purple_debug_info("QQ", "level: %d, uid %d, tmOnline: %d, tmRemainder: %d\n",
+			level, uid, onlineTime, timeRemainder);
 
 	bd = qq_buddy_data_find(gc, uid);
 	if (bd == NULL) {
--- a/libpurple/protocols/qq/buddy_info.h	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/buddy_info.h	Thu Dec 11 06:24:14 2008 +0000
@@ -88,4 +88,6 @@
 void qq_request_get_level_2007(PurpleConnection *gc, guint32 uid);
 void qq_request_get_buddies_level(PurpleConnection *gc, gint update_class);
 void qq_process_get_level_reply(guint8 *buf, gint buf_len, PurpleConnection *gc);
+
+void qq_update_buddy_icon(PurpleAccount *account, const gchar *who, gint face);
 #endif
--- a/libpurple/protocols/qq/buddy_list.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/buddy_list.c	Thu Dec 11 06:24:14 2008 +0000
@@ -148,10 +148,9 @@
 	/* 015-030: unknown key */
 	bytes += qq_getdata(&(bs->unknown_key[0]), QQ_KEY_LENGTH, data + bytes);
 
-	purple_debug_info("QQ_STATUS",
-			"uid: %d, U1: %d, ip: %s:%d, U2:%d, status:%d, U3:%04X\n",
-			bs->uid, bs->unknown1, inet_ntoa(bs->ip), bs->port,
-			bs->unknown2, bs->status, bs->unknown3);
+	purple_debug_info("QQ", "Status:%d, uid: %u, ip: %s:%d, U: %d - %d - %04X\n",
+			bs->status, bs->uid, inet_ntoa(bs->ip), bs->port,
+			bs->unknown1, bs->unknown2, bs->unknown3);
 
 	return bytes;
 }
@@ -163,6 +162,8 @@
 	gint bytes, bytes_start;
 	gint count;
 	guint8  position;
+	gchar *who;
+	PurpleBuddy *buddy;
 	qq_buddy_data *bd;
 	int entry_len = 38;
 
@@ -220,17 +221,23 @@
 		}	/* check if it is a valid entry */
 
 		if (bs.uid == qd->uid) {
-			purple_debug_warning("QQ", "I am in online list %d\n", bs.uid);
+			purple_debug_warning("QQ", "I am in online list %u\n", bs.uid);
 		}
 
 		/* update buddy information */
-		bd = qq_buddy_data_find(gc, bs.uid);
+		who = uid_to_purple_name(bs.uid);
+		buddy = purple_find_buddy(gc->account, who);
+		g_free(who);
+		if (buddy == NULL) {
+			/* create no-auth buddy */
+			buddy = qq_buddy_new(gc, bs.uid);
+		}
+		bd = (buddy == NULL) ? NULL : (qq_buddy_data *)buddy->proto_data;
 		if (bd == NULL) {
 			purple_debug_error("QQ",
-					"Got an online buddy %d, but not in my buddy list\n", bs.uid);
+					"Got an online buddy %u, but not in my buddy list\n", bs.uid);
 			continue;
 		}
-		/* we find one and update qq_buddy_data */
 		/*
 		if(0 != fe->s->client_tag)
 			q_bud->client_tag = fe->s->client_tag;
@@ -313,7 +320,8 @@
 
 		if (bd.uid == 0 || (bytes - buddy_bytes) != bytes_expected) {
 			purple_debug_info("QQ",
-					"Buddy entry, expect %d bytes, read %d bytes\n", bytes_expected, bytes - buddy_bytes);
+					"Buddy entry, expect %d bytes, read %d bytes\n",
+					bytes_expected, bytes - buddy_bytes);
 			g_free(bd.nickname);
 			continue;
 		} else {
@@ -387,7 +395,7 @@
 		/* 05: skip unknow 0x00 */
 		bytes += 1;
 		if (uid == 0 || (type != 0x1 && type != 0x4)) {
-			purple_debug_info("QQ", "Buddy entry, uid=%d, type=%d", uid, type);
+			purple_debug_info("QQ", "Buddy entry, uid=%u, type=%d", uid, type);
 			continue;
 		}
 		if(0x1 == type) { /* a buddy */
@@ -397,7 +405,7 @@
 		} else { /* a group */
 			rmd = qq_room_data_find(gc, uid);
 			if(rmd == NULL) {
-				purple_debug_info("QQ", "Unknow room id %d", uid);
+				purple_debug_info("QQ", "Unknow room id %u", uid);
 				qq_send_room_cmd_only(gc, QQ_ROOM_CMD_GET_INFO, uid);
 			} else {
 				rmd->my_role = QQ_ROOM_ROLE_YES;
@@ -528,6 +536,8 @@
 	qq_data *qd;
 	gint bytes;
 	guint32 my_uid;
+	gchar *who;
+	PurpleBuddy *buddy;
 	qq_buddy_data *bd;
 	qq_buddy_status bs;
 
@@ -549,9 +559,17 @@
 	 * QQ_BUDDY_ONLINE_INVISIBLE */
 	bytes += qq_get32(&my_uid, data + bytes);
 
-	bd = qq_buddy_data_find(gc, bs.uid);
+	/* update buddy information */
+	who = uid_to_purple_name(bs.uid);
+	buddy = purple_find_buddy(gc->account, who);
+	g_free(who);
+	if (buddy == NULL) {
+		/* create no-auth buddy */
+		buddy = qq_buddy_new(gc, bs.uid);
+	}
+	bd = (buddy == NULL) ? NULL : (qq_buddy_data *) buddy->proto_data;
 	if (bd == NULL) {
-		purple_debug_warning("QQ", "Get status of unknown buddy %d\n", bs.uid);
+		purple_debug_warning("QQ", "Got status of no-auth buddy %u\n", bs.uid);
 		return;
 	}
 
@@ -582,8 +600,6 @@
 
 	g_return_if_fail(uid != 0);
 
-	who = uid_to_purple_name(uid);
-
 	/* purple supports signon and idle time
 	 * but it is not much use for QQ, I do not use them */
 	/* serv_got_update(gc, name, online, 0, q_bud->signon, q_bud->idle, bud->uc); */
@@ -612,7 +628,9 @@
 		purple_debug_error("QQ", "unknown status: 0x%X\n", status);
 		break;
 	}
-	purple_debug_info("QQ", "Update buddy %s status as %s\n", who, status_id);
+
+	purple_debug_info("QQ", "buddy %u status = %s\n", uid, status_id);
+	who = uid_to_purple_name(uid);
 	purple_prpl_got_user_status(gc->account, who, status_id, NULL);
 
 	if (flag & QQ_COMM_FLAG_MOBILE && status != QQ_BUDDY_OFFLINE)
--- a/libpurple/protocols/qq/buddy_opt.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/buddy_opt.c	Thu Dec 11 06:24:14 2008 +0000
@@ -110,11 +110,11 @@
 	g_free(who);
 
 	if (buddy == NULL) {
-		purple_debug_error("QQ", "Can not find purple buddy of %d\n", uid);
+		purple_debug_error("QQ", "Can not find purple buddy of %u\n", uid);
 		return NULL;
 	}
 	if (buddy->proto_data == NULL) {
-		purple_debug_error("QQ", "Can not find buddy data of %d\n", uid);
+		purple_debug_error("QQ", "Can not find buddy data of %u\n", uid);
 		return NULL;
 	}
 	return (qq_buddy_data *)buddy->proto_data;
@@ -146,9 +146,8 @@
 		return NULL;
 	}
 
+	purple_debug_info("QQ", "Add new purple buddy: [%u]\n", uid);
 	who = uid_to_purple_name(uid);
-
-	purple_debug_info("QQ", "Add new purple buddy: [%s]\n", who);
 	buddy = purple_buddy_new(gc->account, who, NULL);	/* alias is NULL */
 	buddy->proto_data = NULL;
 
@@ -214,7 +213,7 @@
 
 	g_return_if_fail(uid > 0);
 
-	g_snprintf(uid_str, sizeof(uid_str), "%d", uid);
+	g_snprintf(uid_str, sizeof(uid_str), "%u", uid);
 	bytes = strlen(uid_str);
 	qq_send_cmd_mess(gc, QQ_CMD_REMOVE_BUDDY, (guint8 *) uid_str, bytes, 0, uid);
 }
@@ -234,7 +233,7 @@
 	bytes += qq_put8(raw_data + bytes, auth_len);
 	bytes += qq_putdata(raw_data + bytes, auth, auth_len);
 
-	g_snprintf(uid_str, sizeof(uid_str), "%d", uid);
+	g_snprintf(uid_str, sizeof(uid_str), "%u", uid);
 	bytes += qq_putdata(raw_data + bytes, (guint8 *)uid_str, strlen(uid_str));
 
 	qq_send_cmd_mess(gc, QQ_CMD_REMOVE_BUDDY, raw_data, bytes, 0, uid);
@@ -318,7 +317,7 @@
 	add_req->auth_len = 0;
 
 	who = uid_to_purple_name(uid);
-	msg = g_strdup_printf(_("%d needs Q&A"), uid);
+	msg = g_strdup_printf(_("%u needs Q&A"), uid);
 	purple_request_input(gc, _("Add buddy Q&A"), msg,
 			_("Input answer here"),
 			NULL,
@@ -481,7 +480,7 @@
 	g_return_if_fail(uid > 0);
 
 	/* we need to send the ascii code of this uid to qq server */
-	g_snprintf(uid_str, sizeof(uid_str), "%d", uid);
+	g_snprintf(uid_str, sizeof(uid_str), "%u", uid);
 	qq_send_cmd_mess(gc, QQ_CMD_ADD_BUDDY_NO_AUTH,
 			(guint8 *) uid_str, strlen(uid_str), 0, uid);
 }
@@ -501,25 +500,26 @@
 /* this buddy needs authentication, text conversion is done at lowest level */
 static void request_add_buddy_auth(PurpleConnection *gc, guint32 uid, const gchar response, const gchar *text)
 {
-	gchar *text_qq, uid_str[11];
-	guint8 bar, *raw_data;
-	gint bytes = 0;
+	guint8 raw_data[MAX_PACKET_SIZE - 16];
+	gint bytes;
+	gchar *msg, uid_str[11];
+	guint8 bar;
 
 	g_return_if_fail(uid != 0);
 
-	g_snprintf(uid_str, sizeof(uid_str), "%d", uid);
+	g_snprintf(uid_str, sizeof(uid_str), "%u", uid);
 	bar = 0x1f;
-	raw_data = g_newa(guint8, QQ_MSG_IM_MAX);
 
+	bytes = 0;
 	bytes += qq_putdata(raw_data + bytes, (guint8 *) uid_str, strlen(uid_str));
 	bytes += qq_put8(raw_data + bytes, bar);
 	bytes += qq_put8(raw_data + bytes, response);
 
 	if (text != NULL) {
-		text_qq = utf8_to_qq(text, QQ_CHARSET_DEFAULT);
+		msg = utf8_to_qq(text, QQ_CHARSET_DEFAULT);
 		bytes += qq_put8(raw_data + bytes, bar);
-		bytes += qq_putdata(raw_data + bytes, (guint8 *) text_qq, strlen(text_qq));
-		g_free(text_qq);
+		bytes += qq_putdata(raw_data + bytes, (guint8 *) msg, strlen(msg));
+		g_free(msg);
 	}
 
 	qq_send_cmd(gc, QQ_CMD_ADD_BUDDY_AUTH, raw_data, bytes);
@@ -661,7 +661,7 @@
 	}
 
 	who = uid_to_purple_name(uid);
-	msg = g_strdup_printf(_("%d needs authentication"), uid);
+	msg = g_strdup_printf(_("%u needs authentication"), uid);
 	purple_request_input(gc, _("Add buddy authorize"), msg,
 			_("Input request here"),
 			_("Would you be my friend?"),
@@ -706,7 +706,7 @@
 		return;
 	}
 
-	purple_debug_info("QQ", "Remove buddy with invalid QQ number %d\n", uid);
+	purple_debug_info("QQ", "Remove buddy with invalid QQ number %u\n", uid);
 	qq_buddy_free(buddy);
 }
 
@@ -745,7 +745,7 @@
 
 	buddy = qq_buddy_find(gc, uid);
 	if (data[0] != 0) {
-		msg = g_strdup_printf(_("Failed removing buddy %d"), uid);
+		msg = g_strdup_printf(_("Failed removing buddy %u"), uid);
 		purple_notify_info(gc, _("QQ Buddy"), msg, NULL);
 		g_free(msg);
 	}
@@ -767,7 +767,7 @@
 	qd = (qq_data *) gc->proto_data;
 
 	if (data[0] == 0) {
-		purple_debug_info("QQ", "Reply OK for removing me from %d's buddy list\n", uid);
+		purple_debug_info("QQ", "Reply OK for removing me from %u's buddy list\n", uid);
 		return;
 	}
 	msg = g_strdup_printf(_("Failed removing me from %d's buddy list"), uid);
@@ -788,7 +788,7 @@
 
 	qd = (qq_data *) gc->proto_data;
 
-	purple_debug_info("QQ", "Process buddy add for id [%d]\n", uid);
+	purple_debug_info("QQ", "Process buddy add for id [%u]\n", uid);
 	qq_show_packet("buddy_add_no_auth", data, data_len);
 
 	if (NULL == (segments = split_data(data, data_len, "\x1f", 2)))
@@ -796,7 +796,7 @@
 
 	dest_uid = segments[0];
 	reply = segments[1];
-	if (strtol(dest_uid, NULL, 10) != qd->uid) {	/* should not happen */
+	if (strtoul(dest_uid, NULL, 10) != qd->uid) {	/* should not happen */
 		purple_debug_error("QQ", "Add buddy reply is to [%s], not me!", dest_uid);
 		g_strfreev(segments);
 		return;
@@ -814,7 +814,7 @@
 		}
 		qq_request_get_buddies_online(gc, 0, 0);
 
-		purple_debug_info("QQ", "Successed adding into %d's buddy list", uid);
+		purple_debug_info("QQ", "Successed adding into %u's buddy list", uid);
 		g_strfreev(segments);
 		return;
 	}
@@ -850,7 +850,7 @@
 
 	qd = (qq_data *) gc->proto_data;
 
-	purple_debug_info("QQ", "Process buddy add no auth for id [%d]\n", uid);
+	purple_debug_info("QQ", "Process buddy add no auth for id [%u]\n", uid);
 	qq_show_packet("buddy_add_no_auth_ex", data, data_len);
 
 	bytes = 0;
@@ -860,7 +860,7 @@
 	g_return_if_fail(dest_uid == uid);
 
 	if (reply == 0x99) {
-		purple_debug_info("QQ", "Successed adding buddy %d\n", uid);
+		purple_debug_info("QQ", "Successed adding buddy %u\n", uid);
 		qq_buddy_find_or_new(gc, uid);
 
 		qq_request_buddy_info(gc, uid, 0, 0);
@@ -874,7 +874,7 @@
 	}
 
 	if (reply != 0) {
-		purple_debug_info("QQ", "Failed adding buddy %d, Unknow reply 0x%02X\n",
+		purple_debug_info("QQ", "Failed adding buddy %u, Unknow reply 0x%02X\n",
 			uid, reply);
 	}
 
@@ -943,7 +943,7 @@
 
 	g_return_if_fail(uid != 0 && reason != NULL);
 
-	purple_debug_info("QQ", "Buddy %d request adding, msg: %s\n", uid, reason);
+	purple_debug_info("QQ", "Buddy %u request adding, msg: %s\n", uid, reason);
 
 	add_req = g_new0(qq_buddy_req, 1);
 	add_req->gc = gc;
@@ -973,7 +973,7 @@
 	gchar *msg, *reason;
 
 	g_return_if_fail(from != NULL && to != NULL);
-	uid = strtol(from, NULL, 10);
+	uid = strtoul(from, NULL, 10);
 	g_return_if_fail(uid != 0);
 
 	if (purple_prefs_get_bool("/plugins/prpl/qq/auto_get_authorize_info")) {
@@ -1022,7 +1022,7 @@
 	g_return_if_fail(uid != 0);
 	bytes += qq_get16(&flag1, data + bytes);
 	bytes += qq_get16(&flag2, data + bytes);
-	purple_debug_info("QQ", "Check code reply Ok, uid %d, flag 0x%04X-0x%04X\n",
+	purple_debug_info("QQ", "Check code reply Ok, uid %u, flag 0x%04X-0x%04X\n",
 			uid, flag1, flag2);
 	return;
 }
@@ -1036,7 +1036,7 @@
 
 	g_return_if_fail(code != NULL && code_len > 0 && from != NULL);
 
-	uid = strtol(from, NULL, 10);
+	uid = strtoul(from, NULL, 10);
 	raw_data = g_newa(guint8, code_len + 16);
 	bytes = 0;
 	bytes += qq_put8(raw_data + bytes, 0x03);
@@ -1085,7 +1085,7 @@
 
 	g_return_if_fail(from != NULL && to != NULL);
 	g_return_if_fail(data != NULL && data_len >= 3);
-	uid = strtol(from, NULL, 10);
+	uid = strtoul(from, NULL, 10);
 	g_return_if_fail(uid != 0);
 
 	/* qq_show_packet("server_buddy_add_request_ex", data, data_len); */
@@ -1116,7 +1116,7 @@
 
 	g_return_if_fail(from != NULL && to != NULL);
 
-	uid = strtol(from, NULL, 10);
+	uid = strtoul(from, NULL, 10);
 	who = uid_to_purple_name(uid);
 
 	buddy = purple_find_buddy(account, who);
@@ -1189,7 +1189,7 @@
 
 	qd = (qq_data *) gc->proto_data;
 
-	uid = strtol(from, NULL, 10);
+	uid = strtoul(from, NULL, 10);
 	g_return_if_fail(uid > 0);
 
 	server_buddy_check_code(gc, from, data, data_len);
@@ -1251,7 +1251,7 @@
 	g_free(primary);
 	g_free(secondary);
 
-	uid = strtol(from, NULL, 10);
+	uid = strtoul(from, NULL, 10);
 	g_return_if_fail(uid != 0);
 
 	buddy = qq_buddy_find(gc, uid);
--- a/libpurple/protocols/qq/char_conv.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/char_conv.c	Thu Dec 11 06:24:14 2008 +0000
@@ -27,77 +27,16 @@
 
 #include "char_conv.h"
 #include "packet_parse.h"
-#include "qq.h"
 #include "utils.h"
 
-#define QQ_SMILEY_AMOUNT      96
-
 #define UTF8                  "UTF-8"
 #define QQ_CHARSET_ZH_CN      "GB18030"
 #define QQ_CHARSET_ENG        "ISO-8859-1"
 
 #define QQ_NULL_MSG           "(NULL)"	/* return this if conversion fails */
-#define QQ_NULL_SMILEY        "<IMG ID=\"0\">"	/* return this if smiley conversion fails */
-
-const gchar qq_smiley_map[QQ_SMILEY_AMOUNT] = {
-	0x41, 0x43, 0x42, 0x44, 0x45, 0x46, 0x47, 0x48,
-	0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x73,
-	0x74, 0x75, 0x76, 0x77, 0x8a, 0x8b, 0x8c, 0x8d,
-	0x8e, 0x8f, 0x78, 0x79, 0x7a, 0x7b, 0x90, 0x91,
-	0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
-	0x59, 0x5a, 0x5c, 0x58, 0x57, 0x55, 0x7c, 0x7d,
-	0x7e, 0x7f, 0x9a, 0x9b, 0x60, 0x67, 0x9c, 0x9d,
-	0x9e, 0x5e, 0x9f, 0x89, 0x80, 0x81, 0x82, 0x62,
-	0x63, 0x64, 0x65, 0x66, 0x83, 0x68, 0x84, 0x85,
-	0x86, 0x87, 0x6b, 0x6e, 0x6f, 0x70, 0x88, 0xa0,
-	0x50, 0x51, 0x52, 0x53, 0x54, 0x56, 0x5b, 0x5d,
-	0x5f, 0x61, 0x69, 0x6a, 0x6c, 0x6d, 0x71, 0x72
-};
-
-
-const gchar *purple_smiley_map[QQ_SMILEY_AMOUNT] = {
-	"/jy", "/pz", "/se", "/fd", "/dy", "/ll", "/hx", "/bz",
-	"/shui", "/dk	", "/gg", "/fn", "/tp", "/cy", "/wx", "/ng",
-	"/kuk", "/feid", "/zk", "/tu", "/tx", "/ka", "/by", "/am",
-	"/jie", "/kun", "/jk", "/lh", "/hanx", "/db", "/fendou",
-	"/zhm",
-	"/yiw", "/xu", "/yun", "/zhem", "/shuai", "/kl", "/qiao",
-	"/zj",
-	"/shan", "/fad", "/aiq", "/tiao", "/zhao", "/mm", "/zt",
-	"/maom",
-	"/xg", "/yb", "/qianc", "/dp", "/bei", "/dg", "/shd",
-	"/zhd",
-	"/dao", "/zq", "/yy", "/bb", "/gf", "/fan", "/yw", "/mg",
-	"/dx", "/wen", "/xin", "/xs", "/hy", "/lw", "/dh", "/sj",
-	"/yj", "/ds", "/ty", "/yl", "/qiang", "/ruo", "/ws",
-	"/shl",
-	"/dd", "/mn", "/hl", "/mamao", "/qz", "/fw", "/oh", "/bj",
-	"/qsh", "/xig", "/xy", "/duoy", "/xr", "/xixing", "/nv",
-	"/nan"
-};
-
-/* these functions parse font-attr */
-static gchar _get_size(gchar font_attr)
-{
-	return font_attr & 0x1f;
-}
-
-static gboolean _check_bold(gchar font_attr)
-{
-	return (font_attr & 0x20) ? TRUE : FALSE;
-}
-
-static gboolean _check_italic(gchar font_attr)
-{
-	return (font_attr & 0x40) ? TRUE : FALSE;
-}
-
-static gboolean _check_underline(gchar font_attr)
-{
-	return (font_attr & 0x80) ? TRUE : FALSE;
-}
 
 /* convert a string from from_charset to to_charset, using g_convert */
+/* Warning: do not return NULL */
 static gchar *do_convert(const gchar *str, gssize len, const gchar *to_charset, const gchar *from_charset)
 {
 	GError *error = NULL;
@@ -109,15 +48,12 @@
 	ret = g_convert(str, len, to_charset, from_charset, &byte_read, &byte_write, &error);
 
 	if (error == NULL) {
-		return ret;	/* conversion is OK */
+		return ret;	/* convert is OK */
 	}
 
-	/* conversion error */
+	/* convert error */
 	purple_debug_error("QQ_CONVERT", "%s\n", error->message);
-
-	qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ_CONVERT",
-		(guint8 *) str, (len == -1) ? strlen(str) : len,
-		"Dump failed text");
+	qq_show_packet("Dump failed text", (guint8 *) str, (len == -1) ? strlen(str) : len);
 
 	g_error_free(error);
 	return g_strdup(QQ_NULL_MSG);
@@ -127,6 +63,7 @@
  * take the input as a pascal string and return a converted c-string in UTF-8
  * returns the number of bytes read, return -1 if fatal error
  * the converted UTF-8 will be saved in ret
+ * Return: *ret != NULL
  */
 gint qq_get_vstr(gchar **ret, const gchar *from_charset, guint8 *data)
 {
@@ -162,156 +99,15 @@
 	return 1 + len;
 }
 
-/* convert QQ formatted msg to Purple formatted msg (and UTF-8) */
-gchar *qq_encode_to_purple(guint8 *data, gint len, const gchar *msg, const gint client_version)
-{
-	GString *encoded;
-	guint8 font_attr, font_size, color[3], bar;
-	gboolean is_bold, is_italic, is_underline;
-	guint16 charset_code;
-	gchar *font_name, *color_code, *msg_utf8, *tmp, *ret;
-	gint bytes = 0;
-
-	/* checked qq_show_packet OK */
-	/* qq_show_packet("QQ_MESG recv for font style", data, len); */
-
-	bytes += qq_get8(&font_attr, data + bytes);
-	bytes += qq_getdata(color, 3, data + bytes);	/* red,green,blue */
-	color_code = g_strdup_printf("#%02x%02x%02x", color[0], color[1], color[2]);
-
-	bytes += qq_get8(&bar, data + bytes);	/* skip, not sure of its use */
-	bytes += qq_get16(&charset_code, data + bytes);
-
-	tmp = g_strndup((gchar *)(data + bytes), len - bytes);
-	font_name = qq_to_utf8(tmp, QQ_CHARSET_DEFAULT);
-	g_free(tmp);
-
-	font_size = _get_size(font_attr);
-	is_bold = _check_bold(font_attr);
-	is_italic = _check_italic(font_attr);
-	is_underline = _check_underline(font_attr);
-
-	/* Although there is charset returned from QQ msg, it can't be used.
-	 * For example, if a user send a Chinese message from English Windows
-	 * the charset_code in QQ msg is 0x0000, not 0x8602.
-	 * Therefore, it is better to use uniform conversion.
-	 * By default, we use GBK, which includes all character of SC, TC, and EN. */
-	msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT);
-	encoded = g_string_new("");
-
-	/* Henry: The range QQ sends rounds from 8 to 22, where a font size
-	 * of 10 is equal to 3 in html font tag */
-	g_string_append_printf(encoded,
-			"<font color=\"%s\"><font face=\"%s\"><font size=\"%d\">",
-			color_code, font_name, font_size / 3);
-	purple_debug_info("QQ_MESG",
-			"recv <font color=\"%s\"><font face=\"%s\"><font size=\"%d\">\n",
-			color_code, font_name, font_size / 3);
-	g_string_append(encoded, msg_utf8);
-
-	if (is_bold) {
-		g_string_prepend(encoded, "<b>");
-		g_string_append(encoded, "</b>");
-	}
-	if (is_italic) {
-		g_string_prepend(encoded, "<i>");
-		g_string_append(encoded, "</i>");
-	}
-	if (is_underline) {
-		g_string_prepend(encoded, "<u>");
-		g_string_append(encoded, "</u>");
-	}
-
-	g_string_append(encoded, "</font></font></font>");
-	ret = encoded->str;
-
-	g_free(msg_utf8);
-	g_free(font_name);
-	g_free(color_code);
-	g_string_free(encoded, FALSE);
-
-	return ret;
-}
-
-/* two convenience methods, using do_convert */
+/* Warning: do not return NULL */
 gchar *utf8_to_qq(const gchar *str, const gchar *to_charset)
 {
 	return do_convert(str, -1, to_charset, UTF8);
 }
 
+/* Warning: do not return NULL */
 gchar *qq_to_utf8(const gchar *str, const gchar *from_charset)
 {
 	return do_convert(str, -1, UTF8, from_charset);
 }
 
-/* QQ uses binary code for smiley, while purple uses strings.
- * There is a mapping relation between these two. */
-gchar *qq_smiley_to_purple(gchar *text)
-{
-	gint index;
-	gchar qq_smiley, *cur_seg, **segments, *ret;
-	GString *converted;
-
-	converted = g_string_new("");
-	segments = split_data((guint8 *) text, strlen(text), "\x14\x15", 0);
-	if(segments == NULL)
-		return NULL;
-
-	g_string_append(converted, segments[0]);
-	while ((*(++segments)) != NULL) {
-		cur_seg = *segments;
-		qq_smiley = cur_seg[0];
-		for (index = 0; index < QQ_SMILEY_AMOUNT; index++) {
-			if (qq_smiley_map[index] == qq_smiley)
-				break;
-		}
-		if (index >= QQ_SMILEY_AMOUNT) {
-			g_string_append(converted, QQ_NULL_SMILEY);
-		} else {
-			g_string_append(converted, purple_smiley_map[index]);
-			g_string_append(converted, (cur_seg + 1));
-		}
-	}
-
-	ret = converted->str;
-	g_string_free(converted, FALSE);
-	return ret;
-}
-
-/* convert smiley from purple style to qq binary code */
-gchar *purple_smiley_to_qq(gchar *text)
-{
-	gchar *begin, *cursor, *ret;
-	gint index;
-	GString *converted;
-
-	converted = g_string_new(text);
-
-	for (index = 0; index < QQ_SMILEY_AMOUNT; index++) {
-		begin = cursor = converted->str;
-		while ((cursor = g_strstr_len(cursor, -1, purple_smiley_map[index]))) {
-			g_string_erase(converted, (cursor - begin), strlen(purple_smiley_map[index]));
-			g_string_insert_c(converted, (cursor - begin), 0x14);
-			g_string_insert_c(converted, (cursor - begin + 1), qq_smiley_map[index]);
-			cursor++;
-		}
-	}
-	g_string_append_c(converted, 0x20);	/* important for last smiiley */
-
-	ret = converted->str;
-	g_string_free(converted, FALSE);
-	return ret;
-}
-
-void qq_filter_str(gchar *str) {
-	gchar *temp;
-	if (str == NULL) {
-		return;
-	}
-
-	for (temp = str; *temp != 0; temp++) {
-		/*if (*temp == '\r' || *temp == '\n')  *temp = ' ';*/
-		if (*temp > 0 && *temp < 0x20)  *temp = ' ';
-	}
-	g_strstrip(str);
-}
--- a/libpurple/protocols/qq/char_conv.h	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/char_conv.h	Thu Dec 11 06:24:14 2008 +0000
@@ -32,13 +32,7 @@
 gint qq_get_vstr(gchar **ret, const gchar *from_charset, guint8 *data);
 gint qq_put_vstr(guint8 *buf, const gchar *str_utf8, const gchar *to_charset);
 
-gchar *qq_smiley_to_purple(gchar *text);
-gchar *purple_smiley_to_qq(gchar *text);
-
 gchar *utf8_to_qq(const gchar *str, const gchar *to_charset);
 gchar *qq_to_utf8(const gchar *str, const gchar *from_charset);
-gchar *qq_encode_to_purple(guint8 *font_attr_data, gint len, const gchar *msg, const gint client_version);
 
-gchar *qq_im_filter_html(const gchar *text);
-void qq_filter_str(gchar *str);
 #endif
--- a/libpurple/protocols/qq/group.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/group.c	Thu Dec 11 06:24:14 2008 +0000
@@ -40,7 +40,7 @@
 	guint32 ext_id;
 
 	g_return_if_fail(input != NULL);
-	ext_id = strtol(input, NULL, 10);
+	ext_id = strtoul(input, NULL, 10);
 	/* 0x00000000 means search for demo group */
 	qq_request_room_search(gc, ext_id, QQ_ROOM_SEARCH_ONLY);
 }
--- a/libpurple/protocols/qq/group_im.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/group_im.c	Thu Dec 11 06:24:14 2008 +0000
@@ -64,8 +64,12 @@
 	serv_got_joined_chat(gc, rmd->id, rmd->title_utf8);
 	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, rmd->title_utf8, purple_connection_get_account(gc));
 	if (conv != NULL) {
-		topic_utf8 = g_strdup_printf("%d %s", rmd->ext_id, rmd->notice_utf8);
-		purple_debug_info("QQ", "Set chat topic to %s\n", topic_utf8);
+		if (rmd->notice_utf8 != NULL) {
+			topic_utf8 = g_strdup_printf("%u %s", rmd->ext_id, rmd->notice_utf8);
+		} else {
+			topic_utf8 = g_strdup_printf("%u", rmd->ext_id);
+		}
+		purple_debug_info("QQ", "Chat topic = %s\n", topic_utf8);
 		purple_conv_chat_set_topic(PURPLE_CONV_CHAT(conv), NULL, topic_utf8);
 		g_free(topic_utf8);
 
@@ -157,48 +161,6 @@
 	g_list_free(flags);
 }
 
-/* send IM to a group */
-void qq_request_room_send_im(PurpleConnection *gc, guint32 room_id, const gchar *msg)
-{
-	gint data_len, bytes;
-	guint8 *raw_data, *send_im_tail;
-	guint16 msg_len;
-	gchar *msg_filtered;
-
-	g_return_if_fail(room_id != 0 && msg != NULL);
-
-	msg_filtered = purple_markup_strip_html(msg);
-	/* purple_debug_info("QQ", "Send qun mesg filterd: %s\n", msg_filtered); */
-	msg_len = strlen(msg_filtered);
-
-	data_len = 2 + msg_len + QQ_SEND_IM_AFTER_MSG_LEN;
-	raw_data = g_newa(guint8, data_len);
-
-	bytes = 0;
-	bytes += qq_put16(raw_data + bytes, msg_len + QQ_SEND_IM_AFTER_MSG_LEN);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *) msg_filtered, msg_len);
-	send_im_tail = qq_get_send_im_tail(NULL, NULL, NULL,
-			FALSE, FALSE, FALSE,
-			QQ_SEND_IM_AFTER_MSG_LEN);
-	bytes += qq_putdata(raw_data + bytes, send_im_tail, QQ_SEND_IM_AFTER_MSG_LEN);
-	g_free(send_im_tail);
-	g_free(msg_filtered);
-
-	if (bytes == data_len)	/* create OK */
-		qq_send_room_cmd(gc, QQ_ROOM_CMD_SEND_MSG, room_id, raw_data, data_len);
-	else
-		purple_debug_error("QQ",
-				"Fail creating group_im packet, expect %d bytes, build %d bytes\n", data_len, bytes);
-}
-
-/* this is the ACK */
-void qq_process_room_send_im(PurpleConnection *gc, guint8 *data, gint len)
-{
-	/* return should be the internal group id
-	 * but we have nothing to do with it */
-	return;
-}
-
 void qq_room_got_chat_in(PurpleConnection *gc,
 		guint32 room_id, guint32 uid_from, const gchar *msg, time_t in_time)
 {
@@ -208,6 +170,7 @@
 	gchar *from;
 
 	g_return_if_fail(gc != NULL && room_id != 0);
+	g_return_if_fail(msg != NULL);
 
 	conv = purple_find_chat(gc, room_id);
 	rmd = qq_room_data_find(gc, room_id);
@@ -218,6 +181,8 @@
 	}
 
 	if (conv == NULL) {
+		purple_debug_info("QQ", "Conversion of %u is not open, missing from %d:/n%s/v",
+				room_id, uid_from, msg);
 		return;
 	}
 
@@ -225,7 +190,7 @@
 
 		bd = qq_room_buddy_find(rmd, uid_from);
 		if (bd == NULL || bd->nickname == NULL)
-			from = g_strdup_printf("%d", uid_from);
+			from = g_strdup_printf("%u", uid_from);
 		else
 			from = g_strdup(bd->nickname);
 	} else {
@@ -238,10 +203,9 @@
 /* recv an IM from a group chat */
 void qq_process_room_im(guint8 *data, gint data_len, guint32 id, PurpleConnection *gc, guint16 msg_type)
 {
-	gchar *msg_with_purple_smiley, *msg_utf8_encoded;
 	qq_data *qd;
-	gint skip_len;
-	gint bytes ;
+	gchar *msg_smiley, *msg_fmt, *msg_utf8;
+	gint bytes, tail_len;
 	struct {
 		guint32 ext_id;
 		guint8 type8;
@@ -249,87 +213,224 @@
 		guint16 unknown;
 		guint16 msg_seq;
 		time_t send_time;
-		guint32 unknown4;
+		guint32 version;
 		guint16 msg_len;
 		gchar *msg;
-		guint8 *font_attr;
-		gint font_attr_len;
-	} packet;
+	} im_text;
+	guint32 temp_id;
+	guint16 content_type;
+	guint8 frag_count, frag_index;
+	guint16 msg_id;
+	qq_im_format *fmt = NULL;
 
-	g_return_if_fail(data != NULL && data_len > 0);
-
-	/* FIXME: check length here */
-
+	/* at least include im_text.msg_len */
+	g_return_if_fail(data != NULL && data_len > 23);
 	qd = (qq_data *) gc->proto_data;
 
 	/* qq_show_packet("ROOM_IM", data, data_len); */
-
-	memset(&packet, 0, sizeof(packet));
+	memset(&im_text, 0, sizeof(im_text));
 	bytes = 0;
-	bytes += qq_get32(&(packet.ext_id), data + bytes);
-	bytes += qq_get8(&(packet.type8), data + bytes);
+	bytes += qq_get32(&(im_text.ext_id), data + bytes);
+	bytes += qq_get8(&(im_text.type8), data + bytes);
 
 	if(QQ_MSG_TEMP_QUN_IM == msg_type) {
-		bytes += qq_get32(&(id), data + bytes);
+		bytes += qq_get32(&temp_id, data + bytes);
 	}
 
-	bytes += qq_get32(&(packet.member_uid), bytes + data);
-	bytes += qq_get16(&packet.unknown, data + bytes);	/* 0x0001? */
-	bytes += qq_get16(&(packet.msg_seq), data + bytes);
-	bytes += qq_getime(&packet.send_time, data + bytes);
-	bytes += qq_get32(&packet.unknown4, data + bytes);	/* versionID */
-	/*
-	 * length includes font_attr
-	 * this msg_len includes msg and font_attr
-	 **** the format is ****
-	 * length of all
-	 * 1. unknown 10 bytes
-	 * 2. 0-ended string
-	 * 3. font_attr
-	 */
+	bytes += qq_get32(&(im_text.member_uid), bytes + data);
+	bytes += qq_get16(&im_text.unknown, data + bytes);	/* 0x0001? */
+	bytes += qq_get16(&(im_text.msg_seq), data + bytes);
+	bytes += qq_getime(&im_text.send_time, data + bytes);
+	bytes += qq_get32(&im_text.version, data + bytes);
+	bytes += qq_get16(&(im_text.msg_len), data + bytes);
+	purple_debug_info("QQ", "Room IM, ext id %u, seq %u, version 0x%04X, len %u\n",
+		im_text.ext_id, im_text.msg_seq, im_text.version, im_text.msg_len);
+
+	if (im_text.msg_len != data_len - bytes) {
+		purple_debug_warning("QQ", "Room IM length %d should be %d\n",
+			im_text.msg_len, data_len - bytes);
+		im_text.msg_len = data_len - bytes;
+	}
 
-	bytes += qq_get16(&(packet.msg_len), data + bytes);
-	g_return_if_fail(packet.msg_len > 0);
-	/*
-	 * 10 bytes from lumaqq
-	 *    contentType = buf.getChar();
-	 *    totalFragments = buf.get() & 255;
-	 *    fragmentSequence = buf.get() & 255;
-	 *    messageId = buf.getChar();
-	 *    buf.getInt();
-	 */
+	g_return_if_fail(im_text.msg_len > 0 && bytes + im_text.msg_len <= data_len);
+	if(msg_type != QQ_MSG_QUN_IM_UNKNOWN) {
+		g_return_if_fail(im_text.msg_len >= 10);
 
-	if(msg_type != QQ_MSG_UNKNOWN_QUN_IM)
-		skip_len = 10;
-	else
-		skip_len = 0;
-	bytes += skip_len;
+		bytes += qq_get16(&content_type, data + bytes);
+		bytes += qq_get8(&frag_count, data + bytes);
+		bytes += qq_get8(&frag_index, data + bytes);
+		bytes += qq_get16(&msg_id, data + bytes);
+		bytes += 4;	/* skip 0x(00 00 00 00) */
+		purple_debug_info("QQ", "Room IM, content %d, frag %d-%d, msg id %u\n",
+			content_type, frag_count, frag_index, msg_id);
+		im_text.msg_len -= 10;
+	}
+	g_return_if_fail(im_text.msg_len > 0);
 
 	/* qq_show_packet("Message", data + bytes, data_len - bytes); */
-
-	packet.msg = g_strdup((gchar *) data + bytes);
-	bytes += strlen(packet.msg) + 1;
-	/* there might not be any font_attr, check it */
-	packet.font_attr_len = data_len - bytes;
-	if (packet.font_attr_len > 0) {
-		packet.font_attr = g_memdup(data + bytes, packet.font_attr_len);
-		/* qq_show_packet("font_attr", packet.font_attr, packet.font_attr_len); */
+	if (frag_count <= 1 || frag_count == frag_index + 1) {
+		fmt = qq_im_fmt_new();
+		tail_len = qq_get_im_tail(fmt, data + bytes, data_len - bytes);
+		im_text.msg = g_strndup((gchar *)(data + bytes), data_len - tail_len);
 	} else {
-		packet.font_attr = NULL;
+		im_text.msg = g_strndup((gchar *)(data + bytes), data_len - bytes);
 	}
 
 	/* group im_group has no flag to indicate whether it has font_attr or not */
-	msg_with_purple_smiley = qq_smiley_to_purple(packet.msg);
-	if (packet.font_attr_len > 0) {
-		msg_utf8_encoded = qq_encode_to_purple(packet.font_attr,
-				packet.font_attr_len, msg_with_purple_smiley, qd->client_version);
+	msg_smiley = qq_emoticon_to_purple(im_text.msg);
+	if (fmt != NULL) {
+		msg_fmt = qq_im_fmt_to_purple(fmt, msg_smiley);
+		msg_utf8 =  qq_to_utf8(msg_fmt, QQ_CHARSET_DEFAULT);
+		g_free(msg_fmt);
+		qq_im_fmt_free(fmt);
 	} else {
-		msg_utf8_encoded = qq_to_utf8(msg_with_purple_smiley, QQ_CHARSET_DEFAULT);
+		msg_utf8 =  qq_to_utf8(msg_smiley, QQ_CHARSET_DEFAULT);
+	}
+	g_free(msg_smiley);
+
+	purple_debug_info("QQ", "Room (%u) IM from %u: %s\n",
+			im_text.ext_id, im_text.member_uid, msg_utf8);
+ 	qq_room_got_chat_in(gc, id, im_text.member_uid, msg_utf8, im_text.send_time);
+
+	g_free(msg_utf8);
+	g_free(im_text.msg);
+}
+
+/* send IM to a group */
+static void request_room_send_im(PurpleConnection *gc, guint32 room_id, qq_im_format *fmt, const gchar *msg)
+{
+	guint8 raw_data[MAX_PACKET_SIZE - 16];
+	gint bytes;
+
+	g_return_if_fail(room_id != 0 && msg != NULL);
+
+	bytes = 0;
+	bytes += qq_put16(raw_data + bytes, 0);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)msg, strlen(msg));
+	bytes += qq_put_im_tail(raw_data + bytes, fmt);
+	/* reset first two bytes */
+	qq_put16(raw_data, bytes - 2);
+
+	qq_send_room_cmd(gc, QQ_ROOM_CMD_SEND_IM, room_id, raw_data, bytes);
+}
+
+/* this is the ACK */
+void qq_process_room_send_im(PurpleConnection *gc, guint8 *data, gint len)
+{
+	/* return should be the internal group id
+	 * but we have nothing to do with it */
+	return;
+}
+
+void qq_process_room_send_im_ex(PurpleConnection *gc, guint8 *data, gint len)
+{
+	/* return should be the internal group id
+	 * but we have nothing to do with it */
+	return;
+}
+
+static void request_room_send_im_ex(PurpleConnection *gc, guint32 room_id,
+	qq_im_format *fmt, gchar *msg, guint16 msg_id, guint8 frag_count, guint8 frag_index)
+{
+	guint8 raw_data[MAX_PACKET_SIZE - 16];
+	gint bytes;
+
+
+	g_return_if_fail(room_id != 0 && msg != NULL);
+
+	bytes = 0;
+	bytes += qq_put16(raw_data + bytes, 0);			/* packet len */
+	/* type 0x0001, text only; 0x0002, with custom emoticon */
+	bytes += qq_put16(raw_data + bytes, 0x0001);
+	bytes += qq_put8(raw_data + bytes, frag_count);
+	bytes += qq_put8(raw_data + bytes, frag_index);
+	bytes += qq_put16(raw_data + bytes, msg_id);
+	bytes += qq_put32(raw_data + bytes, 0);			/* unknow 4 bytes */
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)msg, strlen(msg));
+	if (frag_count == frag_index + 1) {
+		bytes += qq_put8(raw_data + bytes, 0x20);	/* add extra SPACE */
+		bytes += qq_put_im_tail(raw_data + bytes, fmt);
 	}
- 	qq_room_got_chat_in(gc, id, packet.member_uid, msg_utf8_encoded, packet.send_time);
+
+	/* reset first two bytes as length */
+	qq_put16(raw_data, bytes - 2);
+
+	/*qq_show_packet("QQ_ROOM_CMD_SEND_IM_EX", raw_data, bytes); */
+	qq_send_room_cmd(gc, QQ_ROOM_CMD_SEND_IM_EX, room_id, raw_data, bytes);
+}
+
+/* send a chat msg to a QQ Qun
+ * called by purple */
+int qq_chat_send(PurpleConnection *gc, int id, const char *what, PurpleMessageFlags flags)
+{
+	qq_data *qd;
+	qq_im_format *fmt;
+	gchar *msg_stripped, *tmp;
+	GSList *segments, *it;
+	gint msg_len;
+	const gchar *start_invalid;
+	gboolean is_smiley_none;
+	guint8 frag_count, frag_index;
+
+	g_return_val_if_fail(NULL != gc && NULL != gc->proto_data, -1);
+	g_return_val_if_fail(id != 0 && what != NULL, -1);
+
+	qd = (qq_data *) gc->proto_data;
+	purple_debug_info("QQ", "Send chat IM to %u, len %d:\n%s\n", id, strlen(what), what);
+
+	/* qq_show_packet("chat IM UTF8", (guint8 *)what, strlen(what)); */
+
+	fmt = qq_im_fmt_new_by_purple(what);
+	is_smiley_none = qq_im_smiley_none(what);
+
+	msg_stripped = purple_markup_strip_html(what);
+	g_return_val_if_fail(msg_stripped != NULL, -1);
+	/* qq_show_packet("IM Stripped", (guint8 *)what, strlen(what)); */
 
-	g_free(msg_with_purple_smiley);
-	g_free(msg_utf8_encoded);
-	g_free(packet.msg);
-	g_free(packet.font_attr);
+	/* Check and valid utf8 string */
+	msg_len = strlen(msg_stripped);
+	if (!g_utf8_validate(msg_stripped, msg_len, &start_invalid)) {
+		if (start_invalid > msg_stripped) {
+			tmp = g_strndup(msg_stripped, start_invalid - msg_stripped);
+			g_free(msg_stripped);
+			msg_stripped = g_strconcat(tmp, _("(Invalid UTF-8 string)"), NULL);
+			g_free(tmp);
+		} else {
+			g_free(msg_stripped);
+			msg_stripped = g_strdup(_("(Invalid UTF-8 string)"));
+		}
+	}
+
+	is_smiley_none = qq_im_smiley_none(what);
+	segments = qq_im_get_segments(msg_stripped, is_smiley_none);
+	g_free(msg_stripped);
+
+	if (segments == NULL) {
+		return -1;
+	}
+
+	qd->send_im_id++;
+	fmt = qq_im_fmt_new_by_purple(what);
+	frag_count = g_slist_length(segments);
+	frag_index = 0;
+/*
+	if (frag_count <= 1) {
+*/
+		for (it = segments; it; it = it->next) {
+			request_room_send_im(gc, id, fmt, (gchar *)it->data);
+			g_free(it->data);
+		}
+/*
+	} else {
+		for (it = segments; it; it = it->next) {
+			request_room_send_im_ex(gc, id, fmt, (gchar *)it->data,
+					qd->send_im_id, frag_count, frag_index);
+			g_free(it->data);
+			frag_index++;
+		}
+	}
+*/
+	qq_im_fmt_free(fmt);
+	g_slist_free(segments);
+	return 1;
 }
--- a/libpurple/protocols/qq/group_im.h	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/group_im.h	Thu Dec 11 06:24:14 2008 +0000
@@ -36,8 +36,9 @@
 void qq_room_got_chat_in(PurpleConnection *gc,
 		guint32 room_id, guint32 uid_from, const gchar *msg, time_t in_time);
 
-void qq_request_room_send_im(PurpleConnection *gc, guint32 room_id, const gchar *msg);
+int qq_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags);
 void qq_process_room_send_im(PurpleConnection *gc, guint8 *data, gint len);
+void qq_process_room_send_im_ex(PurpleConnection *gc, guint8 *data, gint len);
 
 void qq_process_room_im(guint8 *data, gint data_len, guint32 id, PurpleConnection *gc, guint16 msg_type);
 
--- a/libpurple/protocols/qq/group_info.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/group_info.c	Thu Dec 11 06:24:14 2008 +0000
@@ -144,7 +144,7 @@
 
 	purple_notify_user_info_add_section_break(room_info);
 
-	utf8_value = g_strdup_printf(("%d"), rmd->creator_uid);
+	utf8_value = g_strdup_printf(("%u"), rmd->creator_uid);
 	purple_notify_user_info_add_pair(room_info, _("Creator"), utf8_value);
 	g_free(utf8_value);
 
@@ -160,7 +160,7 @@
 	purple_notify_user_info_add_pair(room_info, _("Authorize"), utf8_value);
 	g_free(utf8_value);
 
-	utf8_value = g_strdup_printf(("%d"), rmd->ext_id);
+	utf8_value = g_strdup_printf(("%u"), rmd->ext_id);
 	purple_notify_userinfo(gc, utf8_value, room_info, NULL, NULL);
 	g_free(utf8_value);
 
@@ -214,7 +214,7 @@
 	 * 2(qunNoticeLen), qunNoticeLen(qunNoticeContent, 1(qunDescLen),
 	 * qunDestLen(qunDestcontent)) */
 	bytes += qq_get8(&unknown1, data + bytes);
-	purple_debug_info("QQ", "type=%u creatorid=%u category=%u maxmembers=%u\n",
+	purple_debug_info("QQ", "type: %u creator: %u category: %u maxmembers: %u\n",
 			rmd->type8, rmd->creator_uid, rmd->category, max_members);
 
 	if (qd->client_version >= 2007) {
@@ -241,7 +241,7 @@
 
 #if 0
 		if(organization != 0 || role != 0) {
-			purple_debug_info("QQ_GRP", "%d, organization=%d, role=%d\n", member_uid, organization, role);
+			purple_debug_info("QQ", "%u, organization=%d, role=%d\n", member_uid, organization, role);
 		}
 #endif
 
@@ -277,7 +277,7 @@
 		return;
 	}
 
-	topic_utf8 = g_strdup_printf("%d %s", rmd->ext_id, rmd->notice_utf8);
+	topic_utf8 = g_strdup_printf("%u %s", rmd->ext_id, rmd->notice_utf8);
 	purple_debug_info("QQ", "Set chat topic to %s\n", topic_utf8);
 	purple_conv_chat_set_topic(PURPLE_CONV_CHAT(conv), NULL, topic_utf8);
 	g_free(topic_utf8);
@@ -285,7 +285,7 @@
 
 void qq_process_room_cmd_get_onlines(guint8 *data, gint len, PurpleConnection *gc)
 {
-	guint32 id, member_uid;
+	guint32 room_id, member_uid;
 	guint8 unknown;
 	gint bytes, num;
 	qq_room_data *rmd;
@@ -299,13 +299,13 @@
 	}
 
 	bytes = 0;
-	bytes += qq_get32(&id, data + bytes);
+	bytes += qq_get32(&room_id, data + bytes);
 	bytes += qq_get8(&unknown, data + bytes);	/* 0x3c ?? */
-	g_return_if_fail(id > 0);
+	g_return_if_fail(room_id > 0);
 
-	rmd = qq_room_data_find(gc, id);
+	rmd = qq_room_data_find(gc, room_id);
 	if (rmd == NULL) {
-		purple_debug_error("QQ", "We have no group info for internal id [%d]\n", id);
+		purple_debug_error("QQ", "Can not info of room id [%u]\n", room_id);
 		return;
 	}
 
@@ -384,7 +384,7 @@
 		purple_debug_error("QQ",
 				"group_cmd_get_members_info: Dangerous error! maybe protocol changed, notify developers!");
 	}
-	purple_debug_info("QQ", "Group \"%s\" obtained %d member info\n", rmd->title_utf8, num);
+	purple_debug_info("QQ", "Group \"%s\" got %d member info\n", rmd->title_utf8, num);
 
 	rmd->is_got_buddies = TRUE;
 	qq_room_conv_set_onlines(gc, rmd);
--- a/libpurple/protocols/qq/group_internal.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/group_internal.c	Thu Dec 11 06:24:14 2008 +0000
@@ -34,8 +34,9 @@
 {
 	qq_room_data *rmd;
 
-	purple_debug_info("QQ", "Created room data: %s, ext id %d, id %d\n",
-			title, ext_id, id);
+	purple_debug_info("QQ", "Created room data: %s, ext id %u, id %u\n",
+			title == NULL ? "(NULL)" : title,
+			ext_id, id);
 	rmd = g_new0(qq_room_data, 1);
 	rmd->my_role = QQ_ROOM_ROLE_NO;
 	rmd->id = id;
@@ -60,9 +61,9 @@
 	gchar *value;
 
 	value = g_hash_table_lookup(data, QQ_ROOM_KEY_INTERNAL_ID);
-	id = value ? strtol(value, NULL, 10) : 0;
+	id = value ? strtoul(value, NULL, 10) : 0;
 	value= g_hash_table_lookup(data, QQ_ROOM_KEY_EXTERNAL_ID);
-	ext_id = value ? strtol(value, NULL, 10) : 0;
+	ext_id = value ? strtoul(value, NULL, 10) : 0;
 	value = g_strdup(g_hash_table_lookup(data, QQ_ROOM_KEY_TITLE_UTF8));
 
 	rmd = room_data_new(id, ext_id, value);
@@ -107,10 +108,10 @@
 	}
 	g_hash_table_replace(chat->components,
 		     g_strdup(QQ_ROOM_KEY_INTERNAL_ID),
-		     g_strdup_printf("%d", rmd->id));
+		     g_strdup_printf("%u", rmd->id));
 	g_hash_table_replace(chat->components,
 		     g_strdup(QQ_ROOM_KEY_EXTERNAL_ID),
-		     g_strdup_printf("%d", rmd->ext_id));
+		     g_strdup_printf("%u", rmd->ext_id));
 	g_hash_table_replace(chat->components,
 		     g_strdup(QQ_ROOM_KEY_TITLE_UTF8), g_strdup(rmd->title_utf8));
 }
@@ -121,14 +122,15 @@
 	PurpleGroup *g;
 	PurpleChat *chat;
 
-	purple_debug_info("QQ", "Add new chat: id %d, ext id %d, title %s\n",
-		rmd->id, rmd->ext_id, rmd->title_utf8);
+	purple_debug_info("QQ", "Add new chat: id %u, ext id %u, title %s\n",
+		rmd->id, rmd->ext_id,
+		rmd->title_utf8 == NULL ? "(NULL)" : rmd->title_utf8);
 
 	components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
 	g_hash_table_insert(components,
-			    g_strdup(QQ_ROOM_KEY_INTERNAL_ID), g_strdup_printf("%d", rmd->id));
+			    g_strdup(QQ_ROOM_KEY_INTERNAL_ID), g_strdup_printf("%u", rmd->id));
 	g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_EXTERNAL_ID),
-			    g_strdup_printf("%d", rmd->ext_id));
+			    g_strdup_printf("%u", rmd->ext_id));
 	g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_TITLE_UTF8), g_strdup(rmd->title_utf8));
 
 	chat = purple_chat_new(purple_connection_get_account(gc), rmd->title_utf8, components);
@@ -150,7 +152,7 @@
 
 	g_return_val_if_fail(id != 0 && ext_id != 0, NULL);
 
-	purple_debug_info("QQ", "Find or add new room: id %d, ext id %d\n", id, ext_id);
+	purple_debug_info("QQ", "Find or add new room: id %u, ext id %u\n", id, ext_id);
 
 	rmd = qq_room_data_find(gc, id);
 	if (rmd == NULL) {
@@ -160,7 +162,7 @@
 		qd->groups = g_list_append(qd->groups, rmd);
 	}
 
-	num_str = g_strdup_printf("%d", ext_id);
+	num_str = g_strdup_printf("%u", ext_id);
 	chat = purple_blist_find_chat(purple_connection_get_account(gc), num_str);
 	g_free(num_str);
 	if (chat) {
@@ -181,7 +183,7 @@
 	g_return_if_fail (gc != NULL && gc->proto_data != NULL);
 	qd = (qq_data *) gc->proto_data;
 
-	purple_debug_info("QQ", "Find and remove room data, id %d", id);
+	purple_debug_info("QQ", "Find and remove room data, id %u", id);
 	rmd = qq_room_data_find(gc, id);
 	g_return_if_fail (rmd != NULL);
 
@@ -189,8 +191,8 @@
 	qd->groups = g_list_remove(qd->groups, rmd);
 	room_data_free(rmd);
 
-	purple_debug_info("QQ", "Find and remove chat, ext_id %d", ext_id);
-	num_str = g_strdup_printf("%d", ext_id);
+	purple_debug_info("QQ", "Find and remove chat, ext_id %u", ext_id);
+	num_str = g_strdup_printf("%u", ext_id);
 	chat = purple_blist_find_chat(purple_connection_get_account(gc), num_str);
 	g_free(num_str);
 
--- a/libpurple/protocols/qq/group_join.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/group_join.c	Thu Dec 11 06:24:14 2008 +0000
@@ -116,7 +116,7 @@
 
 	rmd = qq_room_data_find(add_req->gc, add_req->id);
 	if (rmd == NULL) {
-		purple_debug_error("QQ", "Can not find room data of %d\n", add_req->id);
+		purple_debug_error("QQ", "Can not find room data of %u\n", add_req->id);
 		g_free(add_req);
 		return;
 	}
@@ -137,9 +137,9 @@
 	qq_room_req *add_req;
 	g_return_if_fail(rmd != NULL);
 
-	purple_debug_info("QQ", "Room (internal id: %d) needs authentication\n", rmd->id);
+	purple_debug_info("QQ", "Room id %u needs authentication\n", rmd->id);
 
-	msg = g_strdup_printf("QQ Qun %d needs authentication\n", rmd->ext_id);
+	msg = g_strdup_printf("QQ Qun %u needs authentication\n", rmd->ext_id);
 	add_req = g_new0(qq_room_req, 1);
 	add_req->gc = gc;
 	add_req->id = rmd->id;
@@ -154,7 +154,7 @@
 	g_free(msg);
 }
 
-void qq_send_cmd_group_auth(PurpleConnection *gc, qq_room_data *rmd, 
+void qq_send_cmd_group_auth(PurpleConnection *gc, qq_room_data *rmd,
 		guint8 opt, guint32 uid, const gchar *reason_utf8)
 {
 	guint8 raw_data[MAX_PACKET_SIZE - 16];
@@ -219,11 +219,11 @@
 
 	rmd = qq_room_data_find(gc, id);
 	if (rmd != NULL) {
-		msg = g_strdup_printf(_("Successfully joined Qun %s (%d)"), rmd->title_utf8, rmd->ext_id);
-		qq_got_attention(gc, msg);
+		msg = g_strdup_printf(_("Succeeded joining Qun %s (%u)"), rmd->title_utf8, rmd->ext_id);
+		qq_got_message(gc, msg);
 		g_free(msg);
 	} else {
-		qq_got_attention(gc, _("Successfully joined Qun"));
+		qq_got_message(gc, _("Succeeded joining Qun"));
 	}
 }
 
@@ -254,29 +254,29 @@
 	g_return_if_fail(rmd != NULL);
 	switch (reply) {
 	case QQ_ROOM_JOIN_OK:
-		purple_debug_info("QQ", "Successed in joining group \"%s\"\n", rmd->title_utf8);
+		purple_debug_info("QQ", "Succeeded in joining group \"%s\"\n", rmd->title_utf8);
 		rmd->my_role = QQ_ROOM_ROLE_YES;
 		/* this must be shown before getting online members */
 		qq_room_conv_open(gc, rmd);
 		break;
 	case QQ_ROOM_JOIN_NEED_AUTH:
 		purple_debug_info("QQ",
-			   "Fail joining group [%d] %s, needs authentication\n",
+			   "Failed to join room ext id %u %s, needs authentication\n",
 			   rmd->ext_id, rmd->title_utf8);
 		rmd->my_role = QQ_ROOM_ROLE_NO;
 		do_room_join_request(gc, rmd);
 		break;
 	case QQ_ROOM_JOIN_DENIED:
-		msg = g_strdup_printf(_("Qun %d denied to join"), rmd->ext_id);
+		msg = g_strdup_printf(_("Qun %u denied from joining"), rmd->ext_id);
 		purple_notify_info(gc, _("QQ Qun Operation"), _("Failed:"), msg);
 		g_free(msg);
 		break;
 	default:
 		purple_debug_info("QQ",
-			   "Failed joining group [%d] %s, unknown reply: 0x%02x\n",
+			   "Failed to join room ext id %u %s, unknown reply: 0x%02x\n",
 			   rmd->ext_id, rmd->title_utf8, reply);
 
-		purple_notify_info(gc, _("QQ Qun Operation"), _("Failed:"), _("Join Qun, Unknow Reply"));
+		purple_notify_info(gc, _("QQ Qun Operation"), _("Failed:"), _("Join Qun, Unknown Reply"));
 	}
 }
 
@@ -298,7 +298,7 @@
 	purple_debug_info("QQ", "Join room %s, extend id %s\n", id_str, ext_id_str);
 
 	if (id_str != NULL) {
-		id = strtol(id_str, NULL, 10);
+		id = strtoul(id_str, NULL, 10);
 		if (id != 0) {
 			rmd = qq_room_data_find(gc, id);
 			if (rmd) {
@@ -312,7 +312,7 @@
 	if (ext_id_str == NULL) {
 		return;
 	}
-	ext_id = strtol(ext_id_str, NULL, 10);
+	ext_id = strtoul(ext_id_str, NULL, 10);
 	if (ext_id == 0) {
 		return;
 	}
@@ -345,7 +345,7 @@
 	gint bytes = 0;
 	guint8 type;
 
-	purple_debug_info("QQ", "Search QQ Qun %d\n", ext_id);
+	purple_debug_info("QQ", "Search QQ Qun %u\n", ext_id);
 	type = (ext_id == 0x00000000) ? QQ_ROOM_SEARCH_TYPE_DEMO : QQ_ROOM_SEARCH_TYPE_BY_ID;
 
 	bytes = 0;
@@ -361,12 +361,12 @@
 	gchar field[11];
 
 	room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, rmd->title_utf8, NULL);
-	g_snprintf(field, sizeof(field), "%d", rmd->ext_id);
+	g_snprintf(field, sizeof(field), "%u", rmd->ext_id);
 	purple_roomlist_room_add_field(qd->roomlist, room, field);
-	g_snprintf(field, sizeof(field), "%d", rmd->creator_uid);
+	g_snprintf(field, sizeof(field), "%u", rmd->creator_uid);
 	purple_roomlist_room_add_field(qd->roomlist, room, field);
 	purple_roomlist_room_add_field(qd->roomlist, room, rmd->desc_utf8);
-	g_snprintf(field, sizeof(field), "%d", rmd->id);
+	g_snprintf(field, sizeof(field), "%u", rmd->id);
 	purple_roomlist_room_add_field(qd->roomlist, room, field);
 	g_snprintf(field, sizeof(field), "%d", rmd->type8);
 	purple_roomlist_room_add_field(qd->roomlist, room, field);
--- a/libpurple/protocols/qq/group_opt.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/group_opt.c	Thu Dec 11 06:24:14 2008 +0000
@@ -202,7 +202,7 @@
 	rmd = qq_room_data_find(gc, id);
 	g_return_if_fail(rmd != NULL);
 
-	purple_debug_info("QQ", "Succeed in modify members for room %d\n", rmd->ext_id);
+	purple_debug_info("QQ", "Succeed in modify members for room %u\n", rmd->ext_id);
 
 	qq_room_got_chat_in(gc, id, 0, _("Successfully changed Qun member"), now);
 }
@@ -246,7 +246,7 @@
 	bytes += qq_get32(&id, data + bytes);
 	g_return_if_fail(id > 0);
 
-	purple_debug_info("QQ", "Succeed modify room info of %d\n", id);
+	purple_debug_info("QQ", "Successfully modified room info of %u\n", id);
 
 	qq_room_got_chat_in(gc, id, 0, _("Successfully changed Qun information"), now);
 }
@@ -339,7 +339,7 @@
 	qq_send_room_cmd_only(gc, QQ_ROOM_CMD_ACTIVATE, id);
 	qq_update_room(gc, 0, rmd->id);
 
-	purple_debug_info("QQ", "Succeed in create Qun, external ID %d\n", rmd->ext_id);
+	purple_debug_info("QQ", "Succeed in create Qun, ext id %u\n", rmd->ext_id);
 
 	add_req = g_new0(qq_room_req, 1);
 	add_req->gc = gc;
@@ -347,7 +347,7 @@
 
 	purple_request_action(gc, _("QQ Qun Operation"),
 			    _("You have successfully created a Qun"),
-			    _("Would you like to set detailed information now?"),
+			    _("Would you like to set up the detail information now?"),
 			    1,
 				purple_connection_get_account(gc), NULL, NULL,
 				add_req, 2,
@@ -370,7 +370,7 @@
 	rmd = qq_room_data_find(gc, id);
 	g_return_if_fail(rmd != NULL);
 
-	purple_debug_info("QQ", "Succeed in activate Qun %d\n", rmd->ext_id);
+	purple_debug_info("QQ", "Succeed in activate Qun %u\n", rmd->ext_id);
 }
 
 void qq_group_manage_group(PurpleConnection *gc, GHashTable *data)
@@ -382,7 +382,7 @@
 	g_return_if_fail(data != NULL);
 
 	id_ptr = g_hash_table_lookup(data, QQ_ROOM_KEY_INTERNAL_ID);
-	id = strtol(id_ptr, NULL, 10);
+	id = strtoul(id_ptr, NULL, 10);
 	g_return_if_fail(id > 0);
 
 	rmd = qq_room_data_find(gc, id);
@@ -421,13 +421,13 @@
 	add_req->id = id;
 	add_req->member = member_id;
 
-	purple_debug_info("QQ", "%d requested to join room, ext id %d\n", member_id, ext_id);
+	purple_debug_info("QQ", "%u requested to join room, ext id %u\n", member_id, ext_id);
 
 	rmd = qq_room_data_find(gc, id);
 	g_return_if_fail(rmd != NULL);
 	if (qq_room_buddy_find(rmd, member_id)) {
 		purple_debug_info("QQ", "Approve join, buddy joined before\n");
-		msg = g_strdup_printf(_("%d requested to join Qun %d for %s"),
+		msg = g_strdup_printf(_("%u requested to join Qun %u for %s"),
 				member_id, ext_id, reason);
 		qq_room_got_chat_in(gc, id, 0, msg, now);
 		qq_send_cmd_group_auth(gc, rmd, QQ_ROOM_AUTH_REQUEST_APPROVE, member_id, "");
@@ -440,7 +440,7 @@
 		qq_request_buddy_info(gc, member_id, 0, QQ_BUDDY_INFO_DISPLAY);
 	}
 	who = uid_to_purple_name(member_id);
-	msg = g_strdup_printf(_("%d request to join Qun %d"), member_id, ext_id);
+	msg = g_strdup_printf(_("%u request to join Qun %u"), member_id, ext_id);
 
 	purple_request_action(gc, _("QQ Qun Operation"),
 			msg, reason,
@@ -453,7 +453,6 @@
 	g_free(who);
 	g_free(msg);
 	g_free(reason);
-	g_free(reason);
 }
 
 /* the request to join a group is rejected */
@@ -478,7 +477,7 @@
 	bytes += qq_get_vstr(&reason, QQ_CHARSET_DEFAULT, data + bytes);
 
 	msg = g_strdup_printf
-		(_("Failed to join Qun %d, operated by admin %d"), ext_id, admin_uid);
+		(_("Failed to join Qun %u, operated by admin %u"), ext_id, admin_uid);
 
 	purple_notify_warning(gc, _("QQ Qun Operation"), msg, reason);
 
@@ -520,7 +519,7 @@
 		rmd->my_role = QQ_ROOM_ROLE_YES;
 	}
 
-	msg = g_strdup_printf(_("<b>Joining Qun %d is approved by admin %d for %s</b>"),
+	msg = g_strdup_printf(_("<b>Joinning Qun %u is approved by admin %u for %s</b>"),
 			ext_id, admin_uid, reason);
 	now = time(NULL);
 	qq_room_got_chat_in(gc, id, 0, msg, now);
@@ -555,7 +554,7 @@
 		rmd->my_role = QQ_ROOM_ROLE_NO;
 	}
 
-	msg = g_strdup_printf(_("<b>Removed buddy %d.</b>"), uid);
+	msg = g_strdup_printf(_("<b>Removed buddy %u.</b>"), uid);
 	qq_room_got_chat_in(gc, id, 0, msg, now);
 	g_free(msg);
 }
@@ -588,7 +587,7 @@
 
 	qq_update_room(gc, 0, rmd->id);
 
-	msg = g_strdup_printf(_("<b>New buddy %d joined.</b>"), uid);
+	msg = g_strdup_printf(_("<b>New buddy %u joined.</b>"), uid);
 	qq_room_got_chat_in(gc, id, 0, msg, now);
 	g_free(msg);
 }
--- a/libpurple/protocols/qq/im.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/im.c	Thu Dec 11 06:24:14 2008 +0000
@@ -42,7 +42,12 @@
 #include "send_file.h"
 #include "utils.h"
 
-#define DEFAULT_FONT_NAME_LEN 	  4
+#define QQ_MSG_IM_MAX               700	/* max length of IM */
+
+enum {
+	QQ_IM_TEXT = 0x01,
+	QQ_IM_AUTO_REPLY = 0x02
+};
 
 enum
 {
@@ -63,8 +68,6 @@
 };
 
 typedef struct _qq_im_header qq_im_header;
-typedef struct _qq_recv_extended_im_text qq_recv_extended_im_text;
-
 struct _qq_im_header {
 	/* this is the common part of normal_text */
 	guint16 version_from;
@@ -74,75 +77,6 @@
 	guint16 im_type;
 };
 
-#define QQ_SEND_IM_AFTER_MSG_HEADER_LEN 8
-#define DEFAULT_FONT_NAME "\0xcb\0xce\0xcc\0xe5"
-
-guint8 *qq_get_send_im_tail(const gchar *font_color,
-		const gchar *font_size,
-		const gchar *font_name,
-		gboolean is_bold, gboolean is_italic, gboolean is_underline, gint tail_len)
-{
-	gchar *s1;
-	unsigned char *rgb;
-	gint font_name_len;
-	guint8 *send_im_tail;
-	const guint8 simsun[] = { 0xcb, 0xce, 0xcc, 0xe5 };
-
-	if (font_name) {
-		font_name_len = strlen(font_name);
-	} else {
-		font_name_len = DEFAULT_FONT_NAME_LEN;
-		font_name = (const gchar *) simsun;
-	}
-
-	send_im_tail = g_new0(guint8, tail_len);
-
-	g_strlcpy((gchar *) (send_im_tail + QQ_SEND_IM_AFTER_MSG_HEADER_LEN),
-			font_name, tail_len - QQ_SEND_IM_AFTER_MSG_HEADER_LEN);
-	send_im_tail[tail_len - 1] = (guint8) tail_len;
-
-	send_im_tail[0] = 0x00;
-	if (font_size) {
-		send_im_tail[1] = (guint8) (atoi(font_size) * 3 + 1);
-	} else {
-		send_im_tail[1] = 10;
-	}
-	if (is_bold)
-		send_im_tail[1] |= 0x20;
-	if (is_italic)
-		send_im_tail[1] |= 0x40;
-	if (is_underline)
-		send_im_tail[1] |= 0x80;
-
-	if (font_color) {
-		s1 = g_strndup(font_color + 1, 6);
-		/* Henry: maybe this is a bug of purple, the string should have
-		 * the length of odd number @_@
-		 * George Ang: This BUG maybe fixed by Purple. adding new byte
-		 * would cause a crash.
-		 */
-		/* s2 = g_strdup_printf("%sH", s1); */
-		rgb = purple_base16_decode(s1, NULL);
-		g_free(s1);
-		/* g_free(s2); */
-		if (rgb)
-		{
-			memcpy(send_im_tail + 2, rgb, 3);
-			g_free(rgb);
-		} else {
-			send_im_tail[2] = send_im_tail[3] = send_im_tail[4] = 0;
-		}
-	} else {
-		send_im_tail[2] = send_im_tail[3] = send_im_tail[4] = 0;
-	}
-
-	send_im_tail[5] = 0x00;
-	send_im_tail[6] = 0x86;
-	send_im_tail[7] = 0x22;	/* encoding, 0x8622=GB, 0x0000=EN, define BIG5 support here */
-	/* qq_show_packet("QQ_MESG", send_im_tail, tail_len); */
-	return (guint8 *) send_im_tail;
-}
-
 /* read the common parts of the normal_im,
  * returns the bytes read if succeed, or -1 if there is any error */
 static gint get_im_header(qq_im_header *im_header, guint8 *data, gint len)
@@ -159,7 +93,615 @@
 	return bytes;
 }
 
-void qq_got_attention(PurpleConnection *gc, const gchar *msg)
+typedef struct _qq_emoticon qq_emoticon;
+struct _qq_emoticon {
+	guint8 symbol;
+	gchar *name;
+};
+
+static gboolean emoticons_is_sorted = FALSE;
+/* Map for purple smiley convert to qq, need qsort */
+static qq_emoticon emoticons_std[] = {
+	{0x4f, "/:)"},      {0x4f, "/wx"},      {0x4f, "/small_smile"},
+	{0x42, "/:~"},      {0x42, "/pz"},      {0x42, "/curl_lip"},
+	{0x43, "/:*"},      {0x43, "/se"},      {0x43, "/desire"},
+	{0x44, "/:|"},      {0x44, "/fd"},      {0x44, "/dazed"},
+	{0x45, "/8-)"},     {0x45, "/dy"},      {0x45, "/revel"},
+	{0x46, "/:<"},      {0x46, "/ll"},      {0x46, "/cry"},
+	{0x47, "/:$"},      {0x47, "/hx"},      {0x47, "/bashful"},
+	{0x48, "/:x"},      {0x48, "/bz"},      {0x48, "/shut_mouth"},
+	{0x8f, "/|-)"},     {0x8f, "/kun"},     {0x8f, "/sleepy"},
+	{0x49, "/:z"},      {0x49, "/shui"},    {0x49, "/sleep"},	/* after sleepy */
+	{0x4a, "/:'"},      {0x4a, "/dk"},      {0x4a, "/weep"},
+	{0x4b, "/:-|"},     {0x4b, "/gg"},      {0x4b, "/embarassed"},
+	{0x4c, "/:@"},      {0x4c, "/fn"},      {0x4c, "/pissed_off"},
+	{0x4d, "/:P"},      {0x4d, "/tp"},      {0x4d, "/act_up"},
+	{0x4e, "/:D"},      {0x4e, "/cy"},      {0x4e, "/toothy_smile"},
+	{0x41, "/:O"},      {0x41, "/jy"},      {0x41, "/surprised"},
+	{0x73, "/:("},      {0x73, "/ng"},      {0x73, "/sad"},
+	{0x74, "/:+"},      {0x74, "/kuk"},     {0x74, "/cool"},
+	{0xa1, "/--b"},     {0xa1, "/lengh"},
+	{0x76, "/:Q"},      {0x76, "/zk"},      {0x76, "/crazy"},
+	{0x8a, "/;P"},      {0x8a, "/tx"},      {0x8a, "/titter"},
+	{0x8b, "/;-D"},     {0x8b, "/ka"},      {0x8b, "/cute"},
+	{0x8c, "/;d"},      {0x8c, "/by"},      {0x8c, "/disdain"},
+	{0x8d, "/;o"},      {0x8d, "/am"},      {0x8d, "/arrogant"},
+	{0x8e, "/:g"},      {0x8e, "/jie"},     {0x8e, "/starving"},
+	{0x78, "/:!"},      {0x78, "/jk"},      {0x78, "/terror"},
+	{0x79, "/:L"},      {0x79, "/lh"},      {0x79, "/sweat"},
+	{0x7a, "/:>"},      {0x7a, "/hanx"},    {0x7a, "/smirk"},
+	{0x7b, "/:;"},      {0x7b, "/db"},      {0x7b, "/soldier"},
+	{0x90, "/;f"},      {0x90, "/fendou"},  {0x90, "/struggle"},
+	{0x91, "/:-S"},     {0x91, "/zhm"},     {0x91, "/curse"},
+	{0x92, "/?"},       {0x92, "/yiw"},     {0x92, "/question"},
+	{0x93, "/;x"},      {0x93, "/xu"},      {0x93, "/shh"},
+	{0x94, "/;@"},      {0x94, "/yun"},     {0x94, "/dizzy"},
+	{0x95, "/:8"},      {0x95, "/zhem"},    {0x95, "/excrutiating"},
+	{0x96, "/;!"},      {0x96, "/shuai"},   {0x96, "/freaked_out"},
+	{0x97, "/!!!"},     {0x97, "/kl"},      {0x97, "/skeleton"},
+	{0x98, "/xx"},      {0x98, "/qiao"},    {0x98, "/hammer"},
+	{0x99, "/bye"},     {0x99, "/zj"},      {0x99, "/bye"},
+	{0xa2, "/wipe"},    {0xa2, "/ch"},
+	{0xa3, "/dig"},     {0xa3, "/kb"},
+	{0xa4, "/handclap"},{0xa4, "/gz"},
+	{0xa5, "/&-("},     {0xa5, "/qd"},
+	{0xa6, "/B-)"},     {0xa6, "/huaix"},
+	{0xa7, "/<@"},      {0xa7, "/zhh"},
+	{0xa8, "/@>"},      {0xa8, "/yhh"},
+	{0xa9, "/:-O"},     {0xa9, "/hq"},
+	{0xaa, "/>-|"},     {0xaa, "/bs"},
+	{0xab, "/P-("},     {0xab, "/wq"},
+	{0xac, "/:'|"},     {0xac, "/kk"},
+	{0xad, "/X-)"},     {0xad, "/yx"},
+	{0xae, "/:*"},      {0xae, "/qq"},
+	{0xaf, "/@x"},      {0xaf, "/xia"},
+	{0xb0, "/8*"},      {0xb0, "/kel"},
+	{0xb1, "/pd"},      {0xb1, "/cd"},
+	{0x61, "/<W>"},     {0x61, "/xig"},     {0x61, "/watermelon"},
+	{0xb2, "/beer"},    {0xb2, "/pj"},
+	{0xb3, "/basketb"}, {0xb3, "/lq"},
+	{0xb4, "/oo"},      {0xb4, "/pp"},
+	{0x80, "/coffee"},  {0x80, "/kf"},
+	{0x81, "/eat"},     {0x81, "/fan"},
+	{0x62, "/rose"},    {0x62, "/mg"},
+	{0x63, "/fade"},    {0x63, "/dx"},      {0x63, "/wilt"},
+	{0xb5, "/showlove"},{0xb5, "/sa"},		/* after sad */
+	{0x65, "/heart"},   {0x65, "/xin"},
+	{0x66, "/break"},   {0x66, "/xs"},      {0x66, "/broken_heart"},
+	{0x67, "/cake"},    {0x67, "/dg"},
+	{0x9c, "/li"},      {0x9c, "/shd"},     {0x9c, "/lightning"},
+	{0x9d, "/bome"},    {0x9d, "/zhd"},     {0x9d, "/bomb"},
+	{0x9e, "/kn"},      {0x9e, "/dao"},     {0x9e, "/knife"},
+	{0x5e, "/footb"},   {0x5e, "/zq"},      {0x5e, "/soccer"},
+	{0xb6, "/ladybug"}, {0xb6, "/pc"},
+	{0x89, "/shit"},    {0x89, "/bb"},
+	{0x6e, "/moon"},    {0x6e, "/yl"},
+	{0x6b, "/sun"},     {0x6b, "/ty"},
+	{0x68, "/gift"},    {0x68, "/lw"},
+	{0x7f, "/hug"},     {0x7f, "/yb"},
+	{0x6f, "/strong"},  {0x6f, "/qiang"},   {0x6f, "/thumbs_up"},
+	{0x70, "/weak"},    {0x70, "/ruo"},     {0x70, "/thumbs_down"},
+	{0x88, "/share"},   {0x88, "/ws"},      {0x88, "/handshake"},
+	{0xb7, "/@)"},      {0xb7, "/bq"},
+	{0xb8, "/jj"},      {0xb8, "/gy"},
+	{0xb9, "/@@"},      {0xb9, "/qt"},
+	{0xba, "/bad"},     {0xba, "/cj"},
+	{0xbb, "/loveu"},   {0xbb, "/aini"},
+	{0xbc, "/no"},      {0xbc, "/bu"},
+	{0xbd, "/ok"},      {0xbd, "/hd"},
+	{0x5c, "/love"},    {0x5c, "/aiq"},		/* after loveu */
+	{0x56, "/<L>"},     {0x56, "/fw"},      {0x56, "/blow_kiss"},
+	{0x58, "/jump"},    {0x58, "/tiao"},
+	{0x5a, "/shake"},   {0x5a, "/fad"},		/* after fade */
+	{0x5b, "/<O>"},     {0x5b, "/oh"},      {0x5b, "/angry"},
+	{0xbe, "/circle"},  {0xbe, "/zhq"},
+	{0xbf, "/kotow"},   {0xbf, "/kt"},
+	{0xc0, "/turn"},    {0xc0, "/ht"},
+	{0x77, "/:t"},      {0x77, "/tu"},      {0x77, "/vomit"},		/* after turn */
+	{0xa0, "/victory"}, {0xa0, "/shl"},     {0xa0, "/v"},			/* end of v */
+	{0xc1, "/skip"},    {0xc1, "/tsh"},
+	{0xc2, "/oY"},      {0xc2, "/hsh"},
+	{0xc3, "/#-O"},     {0xc3, "/jd"},
+	{0xc4, "/hiphop"},  {0xc4, "/jw"},
+	{0xc5, "/kiss"},    {0xc5, "/xw"},
+	{0xc6, "/<&"},      {0xc6, "/ztj"},
+	{0x7c, "/pig"},     {0x7c, "/zt"},		/* after ztj */
+	{0xc7, "/&>"},      {0xc7, "/ytj"},		/* must be end of "&" */
+	{0x75, "/:#"},      {0x75, "/feid"},    {0x75, "/SARS"},
+	{0x59, "/go"},      {0x59, "/shan"},
+	{0x57, "/find"},    {0x57, "/zhao"},    {0x57, "/search"},
+	{0x55, "/&"},       {0x55, "/mm"},      {0x55, "/beautiful_eyebrows"},
+	{0x7d, "/cat"},     {0x7d, "/maom"},
+	{0x7e, "/dog"},     {0x7e, "/xg"},
+	{0x9a, "/$"},       {0x9a, "/qianc"},   {0x9a, "/money"},
+	{0x9b, "/(!)"},     {0x9b, "/dp"},      {0x9b, "/lightbulb"},
+	{0x60, "/cup"},     {0x60, "/bei"},
+	{0x9f, "/music"},   {0x9f, "/yy"},
+	{0x82, "/pill"},    {0x82, "/yw"},
+	{0x64, "/kiss"},    {0x64, "/wen"},
+	{0x83, "/meeting"}, {0x83, "/hy"},
+	{0x84, "/phone"},   {0x84, "/dh"},
+	{0x85, "/time"},    {0x85, "/sj"},
+	{0x86, "/email"},   {0x86, "/yj"},
+	{0x87, "/tv"},      {0x87, "/ds"},
+	{0x50, "/<D>"},     {0x50, "/dd"},
+	{0x51, "/<J>"},     {0x51,  "/mn"},     {0x51,  "/beauty"},
+	{0x52, "/<H>"},     {0x52,  "/hl"},
+	{0x53, "/<M>"},     {0x53,  "/mamao"},
+	{0x54, "/<QQ>"},    {0x54,  "/qz"},     {0x54,  "/qq"},
+	{0x5d, "/<B>"},     {0x5d,  "/bj"},     {0x5d,  "/baijiu"},
+	{0x5f, "/<U>"},     {0x5f,  "/qsh"},    {0x5f,  "/soda"},
+	{0x69, "/<!!>"},    {0x69,  "/xy"},     {0x69,  "/rain"},
+	{0x6a, "/<~>"},     {0x6a,  "/duoy"},   {0x6a,  "/cloudy"},
+	{0x6c, "/<Z>"},     {0x6c,  "/xr"},     {0x6c,  "/snowman"},
+	{0x6d, "/<*>"},     {0x6d,  "/xixing"}, {0x6d,  "/star"},		/* after starving */
+	{0x71, "/<00>"},    {0x71,  "/nv"},     {0x71,  "/woman"},
+	{0x72, "/<11>"},    {0x72,  "/nan"},    {0x72,  "/man"},
+	{0, NULL}
+};
+gint emoticons_std_num = sizeof(emoticons_std) / sizeof(qq_emoticon) - 1;
+
+/* Map for purple smiley convert to qq, need qsort */
+static qq_emoticon emoticons_ext[] = {
+	{0xc7, "/&>"},		{0xa5, "/&-("},
+	{0xbb, "/loveu"},
+	{0x63, "/fade"},
+	{0x8f, "/sleepy"},	{0x73, "/sad"},		{0x8e, "/starving"},
+	{0xc0, "/turn"},
+	{0xa0, "/victory"}, {0x77, "/vomit"},
+	{0xc6, "/ztj"},
+	{0, NULL}
+};
+gint emoticons_ext_num = sizeof(emoticons_ext) / sizeof(qq_emoticon) - 1;
+
+/* Map for qq smiley convert to purple */
+static qq_emoticon emoticons_sym[] = {
+	{0x41, "/jy"},
+	{0x42, "/pz"},
+	{0x43, "/se"},
+	{0x44, "/fd"},
+	{0x45, "/dy"},
+	{0x46, "/ll"},
+	{0x47, "/hx"},
+	{0x48, "/bz"},
+	{0x49, "/shui"},
+	{0x4a, "/dk"},
+	{0x4b, "/gg"},
+	{0x4c, "/fn"},
+	{0x4d, "/tp"},
+	{0x4e, "/cy"},
+	{0x4f, "/wx"},
+	{0x50, "/dd"},
+	{0x51, "/mn"},
+	{0x52, "/hl"},
+	{0x53, "/mamao"},
+	{0x54, "/qz"},
+	{0x55, "/mm"},
+	{0x56, "/fw"},
+	{0x57, "/zhao"},
+	{0x58, "/tiao"},
+	{0x59, "/shan"},
+	{0x5a, "/fad"},
+	{0x5b, "/oh"},
+	{0x5c, "/aiq"},
+	{0x5d, "/bj"},
+	{0x5e, "/zq"},
+	{0x5f, "/qsh"},
+	{0x60, "/bei"},
+	{0x61, "/xig"},
+	{0x62, "/mg"},
+	{0x63, "/dx"},
+	{0x64, "/wen"},
+	{0x65, "/xin"},
+	{0x66, "/xs"},
+	{0x67, "/dg"},
+	{0x68, "/lw"},
+	{0x69, "/xy"},
+	{0x6a, "/duoy"},
+	{0x6b, "/ty"},
+	{0x6c, "/xr"},
+	{0x6d, "/xixing"},
+	{0x6e, "/yl"},
+	{0x6f, "/qiang"},
+	{0x70, "/ruo"},
+	{0x71, "/nv"},
+	{0x72, "/nan"},
+	{0x73, "/ng"},
+	{0x74, "/kuk"},
+	{0x75, "/feid"},
+	{0x76, "/zk"},
+	{0x77, "/tu"},
+	{0x78, "/jk"},
+	{0x79, "/sweat"},
+	{0x7a, "/hanx"},
+	{0x7b, "/db"},
+	{0x7c, "/zt"},
+	{0x7d, "/maom"},
+	{0x7e, "/xg"},
+	{0x7f, "/yb"},
+	{0x80, "/coffee"},
+	{0x81, "/fan"},
+	{0x82, "/yw"},
+	{0x83, "/hy"},
+	{0x84, "/dh"},
+	{0x85, "/sj"},
+	{0x86, "/yj"},
+	{0x87, "/ds"},
+	{0x88, "/ws"},
+	{0x89, "/bb"},
+	{0x8a, "/tx"},
+	{0x8b, "/ka"},
+	{0x8c, "/by"},
+	{0x8d, "/am"},
+	{0x8e, "/jie"},
+	{0x8f, "/kun"},
+	{0x90, "/fendou"},
+	{0x91, "/zhm"},
+	{0x92, "/yiw"},
+	{0x93, "/xu"},
+	{0x94, "/yun"},
+	{0x95, "/zhem"},
+	{0x96, "/shuai"},
+	{0x97, "/kl"},
+	{0x98, "/qiao"},
+	{0x99, "/zj"},
+	{0x9a, "/qianc"},
+	{0x9b, "/dp"},
+	{0x9c, "/shd"},
+	{0x9d, "/zhd"},
+	{0x9e, "/dao"},
+	{0x9f, "/yy"},
+	{0xa0, "/shl"},
+	{0xa1, "/lengh"},
+	{0xa2, "/wipe"},
+	{0xa3, "/kb"},
+	{0xa4, "/gz"},
+	{0xa5, "/qd"},
+	{0xa6, "/huaix"},
+	{0xa7, "/zhh"},
+	{0xa8, "/yhh"},
+	{0xa9, "/hq"},
+	{0xaa, "/bs"},
+	{0xab, "/wq"},
+	{0xac, "/kk"},
+	{0xad, "/yx"},
+	{0xae, "/qq"},
+	{0xaf, "/xia"},
+	{0xb0, "/kel"},
+	{0xb1, "/cd"},
+	{0xb2, "/pj"},
+	{0xb3, "/lq"},
+	{0xb4, "/pp"},
+	{0xb5, "/sa"},
+	{0xb6, "/pc"},
+	{0xb7, "/bq"},
+	{0xb8, "/gy"},
+	{0xb9, "/qt"},
+	{0xba, "/cj"},
+	{0xbb, "/aini"},
+	{0xbc, "/bu"},
+	{0xbd, "/hd"},
+	{0xbe, "/zhq"},
+	{0xbf, "/kt"},
+	{0xc0, "/ht"},
+	{0xc1, "/tsh"},
+	{0xc2, "/hsh"},
+	{0xc3, "/jd"},
+	{0xc4, "/jw"},
+	{0xc5, "/xw"},
+	{0xc6, "/ztj"},
+	{0xc7, "/ytj"},
+	{0, NULL}
+};
+gint emoticons_sym_num = sizeof(emoticons_sym) / sizeof(qq_emoticon) - 1;;
+
+static int emoticon_cmp(const void *k1, const void *k2)
+{
+	const qq_emoticon *e1 = (const qq_emoticon *) k1;
+	const qq_emoticon *e2 = (const qq_emoticon *) k2;
+	if (e1->symbol == 0) {
+		/* purple_debug_info("QQ", "emoticon_cmp len %d\n", strlen(e2->name)); */
+		return strncmp(e1->name, e2->name, strlen(e2->name));
+	}
+	if (e2->symbol == 0) {
+		/* purple_debug_info("QQ", "emoticon_cmp len %d\n", strlen(e1->name)); */
+		return strncmp(e1->name, e2->name, strlen(e1->name));
+	}
+	return strcmp(e1->name, e2->name);
+}
+
+static void emoticon_try_sort()
+{
+	if (emoticons_is_sorted)
+		return;
+
+	purple_debug_info("QQ", "qsort stand emoticons\n");
+	qsort(emoticons_std, emoticons_std_num, sizeof(qq_emoticon), emoticon_cmp);
+	purple_debug_info("QQ", "qsort extend emoticons\n");
+	qsort(emoticons_ext, emoticons_ext_num, sizeof(qq_emoticon), emoticon_cmp);
+	emoticons_is_sorted = TRUE;
+}
+
+static qq_emoticon *emoticon_find(gchar *name)
+{
+	qq_emoticon *ret = NULL;
+	qq_emoticon key;
+
+	g_return_val_if_fail(name != NULL, NULL);
+	emoticon_try_sort();
+
+	key.name = name;
+	key.symbol = 0;
+
+	/* purple_debug_info("QQ", "bsearch emoticon %.20s\n", name); */
+	ret = (qq_emoticon *)bsearch(&key, emoticons_ext, emoticons_ext_num,
+			sizeof(qq_emoticon), emoticon_cmp);
+	if (ret != NULL) {
+		return ret;
+	}
+	ret = (qq_emoticon *)bsearch(&key, emoticons_std, emoticons_std_num,
+			sizeof(qq_emoticon), emoticon_cmp);
+	return ret;
+}
+
+static gchar *emoticon_get(guint8 symbol)
+{
+	g_return_val_if_fail(symbol >= emoticons_sym[0].symbol, NULL);
+	g_return_val_if_fail(symbol <= emoticons_sym[emoticons_sym_num - 2].symbol, NULL);
+
+	return emoticons_sym[symbol - emoticons_sym[0].symbol].name;
+}
+
+/* convert qq emote icon to purple sytle
+   Notice: text is in qq charset, GB18030 or utf8 */
+gchar *qq_emoticon_to_purple(gchar *text)
+{
+	gchar *ret;
+	GString *converted;
+	gchar **segments;
+	gboolean have_smiley;
+	gchar *purple_smiley;
+	gchar *cur;
+	guint8 symbol;
+
+	/* qq_show_packet("text", (guint8 *)text, strlen(text)); */
+	g_return_val_if_fail(text != NULL && strlen(text) != 0, g_strdup(""));
+
+	segments = g_strsplit_set(text, "\x14\x15", 0);
+	if(segments == NULL) {
+		return g_strdup("");
+	}
+
+	converted = g_string_new("");
+	have_smiley = FALSE;
+	if (segments[0] != NULL) {
+		g_string_append(converted, segments[0]);
+	} else {
+		purple_debug_info("QQ", "segments[0] is NULL\n");
+	}
+	while ((*(++segments)) != NULL) {
+		have_smiley = TRUE;
+
+		cur = *segments;
+		if (cur == NULL) {
+			purple_debug_info("QQ", "current segment is NULL\n");
+			break;
+		}
+		if (strlen(cur) == 0) {
+			purple_debug_info("QQ", "current segment length is 0\n");
+			break;
+		}
+		symbol = (guint8)cur[0];
+
+		purple_smiley = emoticon_get(symbol);
+		if (purple_smiley == NULL) {
+			purple_debug_info("QQ", "Not found smiley of 0x%02X\n", symbol);
+			g_string_append(converted, "<IMG ID=\"0\">");
+		} else {
+			purple_debug_info("QQ", "Found 0x%02X smiley is %s\n", symbol, purple_smiley);
+			g_string_append(converted, purple_smiley);
+			g_string_append(converted, cur + 1);
+		}
+		/* purple_debug_info("QQ", "next segment\n"); */
+	}
+
+	/* purple_debug_info("QQ", "end of convert\n"); */
+	if (!have_smiley) {
+		g_string_prepend(converted, "<font sml=\"none\">");
+		g_string_append(converted, "</font>");
+	}
+	ret = converted->str;
+	g_string_free(converted, FALSE);
+	return ret;
+}
+
+void qq_im_fmt_free(qq_im_format *fmt)
+{
+	g_return_if_fail(fmt != NULL);
+	if (fmt->font)	g_free(fmt->font);
+	g_free(fmt);
+}
+
+qq_im_format *qq_im_fmt_new(void)
+{
+	qq_im_format *fmt;
+	const gchar simsun[] = { 0xcb, 0xce, 0xcc, 0xe5, 0};	/* simsun in Chinese */
+
+	fmt = g_new0(qq_im_format, 1);
+	memset(fmt, 0, sizeof(qq_im_format));
+	fmt->font_len = strlen(simsun);
+	fmt->font = g_strdup(simsun);
+	fmt->attr = 10;
+	/* encoding, 0x8602=GB, 0x0000=EN, define BIG5 support here */
+	fmt->charset = 0x8602;
+
+	return fmt;
+}
+
+qq_im_format *qq_im_fmt_new_by_purple(const gchar *msg)
+{
+	qq_im_format *fmt;
+	const gchar *start, *end, *last;
+	GData *attribs;
+	gchar *tmp;
+	unsigned char *rgb;
+
+	g_return_val_if_fail(msg != NULL, NULL);
+
+	fmt = qq_im_fmt_new();
+
+	last = msg;
+	while (purple_markup_find_tag("font", last, &start, &end, &attribs)) {
+		tmp = g_datalist_get_data(&attribs, "face");
+		if (tmp && strlen(tmp) > 0) {
+			if (fmt->font)	g_free(fmt->font);
+			fmt->font_len = strlen(tmp);
+			fmt->font = g_strdup(tmp);
+		}
+
+		tmp = g_datalist_get_data(&attribs, "size");
+		if (tmp) {
+			fmt->attr = atoi(tmp) * 3 + 1;
+			fmt->attr &= 0x0f;
+		}
+
+		tmp = g_datalist_get_data(&attribs, "color");
+		if (tmp && strlen(tmp) > 1) {
+			rgb = purple_base16_decode(tmp + 1, NULL);
+			g_memmove(fmt->rgb, rgb, 3);
+			g_free(rgb);
+		}
+
+		g_datalist_clear(&attribs);
+		last = end + 1;
+	}
+
+	if (purple_markup_find_tag("b", msg, &start, &end, &attribs)) {
+		fmt->attr |= 0x20;
+		g_datalist_clear(&attribs);
+	}
+
+	if (purple_markup_find_tag("i", msg, &start, &end, &attribs)) {
+		fmt->attr |= 0x40;
+		g_datalist_clear(&attribs);
+	}
+
+	if (purple_markup_find_tag("u", msg, &start, &end, &attribs)) {
+		fmt->attr |= 0x80;
+		g_datalist_clear(&attribs);
+	}
+
+	return fmt;
+}
+
+/* convert qq format to purple
+   Notice: text is in qq charset, GB18030 or utf8 */
+gchar *qq_im_fmt_to_purple(qq_im_format *fmt, gchar *text)
+{
+	GString *converted, *tmp;
+	gchar *ret;
+	gint size;
+
+	converted = g_string_new(text);
+	tmp = g_string_new("");
+	g_string_append_printf(tmp, "<font color=\"#%02x%02x%02x\">",
+		fmt->rgb[0], fmt->rgb[1], fmt->rgb[2]);
+	g_string_prepend(converted, tmp->str);
+	g_string_set_size(tmp, 0);
+	g_string_append(converted, "</font>");
+
+	/* Fixme:
+	 * check font face can be convert to utf8 or not?
+	 * If failed, prepending font face cause msg display as "(NULL)" */
+	if (fmt->font != NULL) {
+		g_string_append_printf(tmp, "<font face=\"%s\">", fmt->font);
+		g_string_prepend(converted, tmp->str);
+		g_string_set_size(tmp, 0);
+		g_string_append(converted, "</font>");
+	}
+	size = (fmt->attr & 0x1f) / 3;
+	if (size >= 0) {
+		g_string_append_printf(tmp, "<font size=\"%d\">", size);
+		g_string_prepend(converted, tmp->str);
+		g_string_set_size(tmp, 0);
+		g_string_append(converted, "</font>");
+	}
+	if (fmt->attr & 0x20) {
+		/* bold */
+		g_string_prepend(converted, "<b>");
+		g_string_append(converted, "</b>");
+	}
+	if (fmt->attr & 0x40) {
+		/* italic */
+		g_string_prepend(converted, "<i>");
+		g_string_append(converted, "</i>");
+	}
+	if (fmt->attr & 0x80) {
+		/* underline */
+		g_string_prepend(converted, "<u>");
+		g_string_append(converted, "</u>");
+	}
+
+	g_string_free(tmp, TRUE);
+	ret = converted->str;
+	g_string_free(converted, FALSE);
+	return ret;
+}
+
+gint qq_put_im_tail(guint8 *buf, qq_im_format *fmt)
+{
+	gint bytes;
+
+	g_return_val_if_fail(buf != NULL && fmt != NULL, 0);
+
+	bytes = 0;
+	bytes += qq_put8(buf + bytes, 0);
+	bytes += qq_put8(buf + bytes, fmt->attr);
+	bytes += qq_putdata(buf + bytes, fmt->rgb, sizeof(fmt->rgb));
+	bytes += qq_put8(buf + bytes, 0);
+	bytes += qq_put16(buf + bytes, fmt->charset);
+	if (fmt->font != NULL && fmt->font_len > 0) {
+		bytes += qq_putdata(buf + bytes, (guint8 *)fmt->font, fmt->font_len);
+	} else {
+		purple_debug_warning("QQ", "Font name is empty\n");
+	}
+	bytes += qq_put8(buf + bytes, bytes + 1);
+	/* qq_show_packet("IM tail", buf, bytes); */
+	return bytes;
+}
+
+/* data includes text msg and font attr*/
+gint qq_get_im_tail(qq_im_format *fmt, guint8 *data, gint data_len)
+{
+	gint bytes, text_len;
+	guint8 tail_len;
+	guint8 font_len;
+
+	g_return_val_if_fail(fmt != NULL && data != NULL, 0);
+	g_return_val_if_fail(data_len > 1, 0);
+	tail_len = data[data_len - 1];
+	g_return_val_if_fail(tail_len > 2, 0);
+	text_len = data_len - tail_len;
+	g_return_val_if_fail(text_len >= 0, 0);
+
+	bytes = text_len;
+	/* qq_show_packet("IM tail", data + bytes, tail_len); */
+	bytes += 1;		/* skip 0x00 */
+	bytes += qq_get8(&fmt->attr, data + bytes);
+	bytes += qq_getdata(fmt->rgb, sizeof(fmt->rgb), data + bytes);	/* red,green,blue */
+ 	bytes += 1;	/* skip 0x00 */
+	bytes += qq_get16(&fmt->charset, data + bytes);
+
+	font_len = data_len - bytes - 1;
+	g_return_val_if_fail(font_len > 0, bytes + 1);
+
+	fmt->font_len = font_len;
+	if (fmt->font != NULL)	g_free(fmt->font);
+	fmt->font = g_strndup((gchar *)data + bytes, fmt->font_len);
+	return tail_len;
+}
+
+void qq_got_message(PurpleConnection *gc, const gchar *msg)
 {
 	qq_data *qd;
 	gchar *from;
@@ -180,27 +722,28 @@
 /* process received normal text IM */
 static void process_im_text(PurpleConnection *gc, guint8 *data, gint len, qq_im_header *im_header)
 {
+	qq_data *qd;
 	guint16 purple_msg_type;
 	gchar *who;
-	gchar *msg_with_purple_smiley;
-	gchar *msg_utf8_encoded;
-	qq_data *qd;
-	gint bytes = 0;
-	PurpleBuddy *b;
+	gchar *msg_smiley, *msg_fmt, *msg_utf8;
+	PurpleBuddy *buddy;
 	qq_buddy_data *bd;
+	gint bytes, tail_len;
+	qq_im_format *fmt = NULL;
 
 	struct {
 		/* now comes the part for text only */
 		guint16 msg_seq;
 		guint32 send_time;
 		guint16 sender_icon;
-		guint8 unknown2[3];
-		guint8 is_there_font_attr;
-		guint8 unknown3[4];
+		guint8 unknown1[3];
+		guint8 has_font_attr;
+		guint8 fragment_count;
+		guint8 fragment_index;
+		guint8 msg_id;
+		guint8 unknown2;
 		guint8 msg_type;
 		gchar *msg;		/* no fixed length, ends with 0x00 */
-		guint8 *font_attr;
-		gint font_attr_len;
 	} im_text;
 
 	g_return_if_fail (data != NULL && len > 0);
@@ -209,99 +752,97 @@
 	qd = (qq_data *) gc->proto_data;
 	memset(&im_text, 0, sizeof(im_text));
 
-	/* push data into im_text */
+	/* qq_show_packet("IM text", data, len); */
+	bytes = 0;
 	bytes += qq_get16(&(im_text.msg_seq), data + bytes);
 	bytes += qq_get32(&(im_text.send_time), data + bytes);
 	bytes += qq_get16(&(im_text.sender_icon), data + bytes);
-	bytes += qq_getdata((guint8 *) & (im_text.unknown2), 3, data + bytes);
-	bytes += qq_get8(&(im_text.is_there_font_attr), data + bytes);
-	/**
-	 * from lumaqq	for unknown3
-	 *	totalFragments = buf.get() & 255;
-	 *	fragmentSequence = buf.get() & 255;
-	 *	messageId = buf.getChar();
-	 */
-	bytes += qq_getdata((guint8 *) & (im_text.unknown3), 4, data + bytes);
+	bytes += qq_getdata(im_text.unknown1, sizeof(im_text.unknown1), data + bytes); /* 0x(00 00 00)*/
+	bytes += qq_get8(&(im_text.has_font_attr), data + bytes);
+	bytes += qq_get8(&(im_text.fragment_count), data + bytes);
+	bytes += qq_get8(&(im_text.fragment_index), data + bytes);
+	bytes += qq_get8(&(im_text.msg_id), data + bytes);
+	bytes += 1; 	/* skip 0x00 */
 	bytes += qq_get8(&(im_text.msg_type), data + bytes);
+	purple_debug_info("QQ", "IM Seq %u, id %04X, fragment %d-%d, type %d, %s\n",
+			im_text.msg_seq, im_text.msg_id,
+			im_text.fragment_count, im_text.fragment_index,
+			im_text.msg_type,
+			im_text.has_font_attr ? "exist font atrr" : "");
 
-	/* we need to check if this is auto-reply
-	 * QQ2003iii build 0304, returns the msg without font_attr
-	 * even the is_there_font_attr shows 0x01, and msg does not ends with 0x00 */
-	if (im_text.msg_type == QQ_IM_AUTO_REPLY) {
-		im_text.is_there_font_attr = 0x00;	/* indeed there is no this flag */
+	if (im_text.has_font_attr) {
+		fmt = qq_im_fmt_new();
+		tail_len = qq_get_im_tail(fmt, data + bytes, len - bytes);
+		im_text.msg = g_strndup((gchar *)(data + bytes), len - tail_len);
+	} else	{
 		im_text.msg = g_strndup((gchar *)(data + bytes), len - bytes);
-	} else {		/* it is normal mesasge */
-		if (im_text.is_there_font_attr) {
-			im_text.msg = g_strdup((gchar *)(data + bytes));
-			bytes += strlen(im_text.msg) + 1; /* length decided by strlen! will it cause a crash? */
-			im_text.font_attr_len = len - bytes;
-			im_text.font_attr = g_memdup(data + bytes, im_text.font_attr_len);
-		} else		/* not im_text.is_there_font_attr */
-			im_text.msg = g_strndup((gchar *)(data + bytes), len - bytes);
-	}			/* if im_text.msg_type */
+	}
+	/* qq_show_packet("IM text", (guint8 *)im_text.msg , strlen(im_text.msg) ); */
 
 	who = uid_to_purple_name(im_header->uid_from);
-	b = purple_find_buddy(gc->account, who);
-	if (b == NULL) {
+	buddy = purple_find_buddy(gc->account, who);
+	if (buddy == NULL) {
 		/* create no-auth buddy */
-		b = qq_buddy_new(gc, im_header->uid_from);
+		buddy = qq_buddy_new(gc, im_header->uid_from);
 	}
-	bd = (b == NULL) ? NULL : (qq_buddy_data *) b->proto_data;
+	bd = (buddy == NULL) ? NULL : (qq_buddy_data *) buddy->proto_data;
 	if (bd != NULL) {
 		bd->client_tag = im_header->version_from;
+		bd->face = im_text.sender_icon;
+		qq_update_buddy_icon(gc->account, who, bd->face);
 	}
 
-	purple_msg_type = (im_text.msg_type == QQ_IM_AUTO_REPLY) ? PURPLE_MESSAGE_AUTO_RESP : 0;
+	purple_msg_type = (im_text.msg_type == QQ_IM_AUTO_REPLY)
+		? PURPLE_MESSAGE_AUTO_RESP : 0;
 
-	msg_with_purple_smiley = qq_smiley_to_purple(im_text.msg);
-	msg_utf8_encoded = im_text.is_there_font_attr ?
-		qq_encode_to_purple(im_text.font_attr,
-				im_text.font_attr_len,
-				msg_with_purple_smiley, qd->client_version)
-		: qq_to_utf8(msg_with_purple_smiley, QQ_CHARSET_DEFAULT);
+	msg_smiley = qq_emoticon_to_purple(im_text.msg);
+	if (fmt != NULL) {
+		msg_fmt = qq_im_fmt_to_purple(fmt, msg_smiley);
+		msg_utf8 =  qq_to_utf8(msg_fmt, QQ_CHARSET_DEFAULT);
+		g_free(msg_fmt);
+		qq_im_fmt_free(fmt);
+	} else {
+		msg_utf8 =  qq_to_utf8(msg_smiley, QQ_CHARSET_DEFAULT);
+	}
+	g_free(msg_smiley);
 
 	/* send encoded to purple, note that we use im_text.send_time,
 	 * not the time we receive the message
 	 * as it may have been delayed when I am not online. */
-	serv_got_im(gc, who, msg_utf8_encoded, purple_msg_type, (time_t) im_text.send_time);
+	purple_debug_info("QQ", "IM from %u: %s\n", im_header->uid_from,msg_utf8);
+	serv_got_im(gc, who, msg_utf8, purple_msg_type, (time_t) im_text.send_time);
 
-	g_free(msg_utf8_encoded);
-	g_free(msg_with_purple_smiley);
+	g_free(msg_utf8);
 	g_free(who);
 	g_free(im_text.msg);
-	if (im_text.font_attr)	g_free(im_text.font_attr);
 }
 
 /* process received extended (2007) text IM */
-static void process_extend_im_text(
-		PurpleConnection *gc, guint8 *data, gint len, qq_im_header *im_header)
+static void process_extend_im_text(PurpleConnection *gc, guint8 *data, gint len, qq_im_header *im_header)
 {
+	qq_data *qd;
 	guint16 purple_msg_type;
 	gchar *who;
-	gchar *msg_with_purple_smiley;
-	gchar *msg_utf8_encoded;
-	qq_data *qd;
-	PurpleBuddy *b;
+	gchar *msg_smiley, *msg_fmt, *msg_utf8;
+	PurpleBuddy *buddy;
 	qq_buddy_data *bd;
-	gint bytes, text_len;
+	gint bytes, tail_len;
+	qq_im_format *fmt = NULL;
 
 	struct {
 		/* now comes the part for text only */
-		guint16 sessionId;
+		guint16 msg_seq;
 		guint32 send_time;
-		guint16 senderHead;
-		guint32 flag;
-		guint8 unknown2[8];
-		guint8 fragmentCount;
-		guint8 fragmentIndex;
-		guint16 messageId;
-		guint8 replyType;
+		guint16 sender_icon;
+		guint32 has_font_attr;
+		guint8 unknown1[8];
+		guint8 fragment_count;
+		guint8 fragment_index;
+		guint8 msg_id;
+		guint8 unknown2;
+		guint8 msg_type;
 		gchar *msg;		/* no fixed length, ends with 0x00 */
 		guint8 fromMobileQQ;
-
-		guint8 is_there_font_attr;
-		guint8 *font_attr;
-		gint8 font_attr_len;
 	} im_text;
 
 	g_return_if_fail (data != NULL && len > 0);
@@ -310,79 +851,69 @@
 	qd = (qq_data *) gc->proto_data;
 	memset(&im_text, 0, sizeof(im_text));
 
-	/* push data into im_text */
+	/* qq_show_packet("Extend IM text", data, len); */
 	bytes = 0;
-	bytes += qq_get16(&(im_text.sessionId), data + bytes);
+	bytes += qq_get16(&(im_text.msg_seq), data + bytes);
 	bytes += qq_get32(&(im_text.send_time), data + bytes);
-	bytes += qq_get16(&(im_text.senderHead), data + bytes);
-	bytes += qq_get32(&(im_text.flag), data + bytes);
-
-	bytes += qq_getdata(im_text.unknown2, 8, data + bytes);
-	bytes += qq_get8(&(im_text.fragmentCount), data + bytes);
-	bytes += qq_get8(&(im_text.fragmentIndex), data + bytes);
-
-	bytes += qq_get16(&(im_text.messageId), data + bytes);
-	bytes += qq_get8(&(im_text.replyType), data + bytes);
-
-	im_text.font_attr_len = data[len-1] & 0xff;
+	bytes += qq_get16(&(im_text.sender_icon), data + bytes);
+	bytes += qq_get32(&(im_text.has_font_attr), data + bytes);
+	bytes += qq_getdata(im_text.unknown1, sizeof(im_text.unknown1), data + bytes);
+	bytes += qq_get8(&(im_text.fragment_count), data + bytes);
+	bytes += qq_get8(&(im_text.fragment_index), data + bytes);
+	bytes += qq_get8(&(im_text.msg_id), data + bytes);
+	bytes += 1; 	/* skip 0x00 */
+	bytes += qq_get8(&(im_text.msg_type), data + bytes);
+	purple_debug_info("QQ", "IM Seq %u, id %04X, fragment %d-%d, type %d, %s\n",
+			im_text.msg_seq, im_text.msg_id,
+			im_text.fragment_count, im_text.fragment_index,
+			im_text.msg_type,
+			im_text.has_font_attr ? "exist font atrr" : "");
 
-	text_len = len - bytes - im_text.font_attr_len;
-	im_text.msg = g_strndup((gchar *)(data + bytes), text_len);
-	bytes += text_len;
-	if(im_text.font_attr_len >= 0)
-		im_text.font_attr = g_memdup(data + bytes, im_text.font_attr_len);
-	else
-	{
-		purple_debug_error("QQ", "Failed to get IM's font attribute len %d\n",
-			im_text.font_attr_len);
-		return;
+	if (im_text.has_font_attr) {
+		fmt = qq_im_fmt_new();
+		tail_len = qq_get_im_tail(fmt, data + bytes, len - bytes);
+		im_text.msg = g_strndup((gchar *)(data + bytes), len - tail_len);
+	} else	{
+		im_text.msg = g_strndup((gchar *)(data + bytes), len - bytes);
 	}
+	/* qq_show_packet("IM text", (guint8 *)im_text.msg , strlen(im_text.msg)); */
 
-	if(im_text.fragmentCount == 0)
-		im_text.fragmentCount = 1;
-
-	/* Filter tail space */
-	if(im_text.fragmentIndex == im_text.fragmentCount -1)
-	{
-		gint real_len = text_len;
-		while(real_len > 0 && im_text.msg[real_len - 1] == 0x20)
-			real_len --;
-
-		text_len = real_len;
-		/* Null string instead of space */
-		im_text.msg[text_len] = 0;
-	}
+	if(im_text.fragment_count == 0) 	im_text.fragment_count = 1;
 
 	who = uid_to_purple_name(im_header->uid_from);
-	b = purple_find_buddy(gc->account, who);
-	if (b == NULL) {
+	buddy = purple_find_buddy(gc->account, who);
+	if (buddy == NULL) {
 		/* create no-auth buddy */
-		b = qq_buddy_new(gc, im_header->uid_from);
+		buddy = qq_buddy_new(gc, im_header->uid_from);
 	}
-	bd = (b == NULL) ? NULL : (qq_buddy_data *) b->proto_data;
+	bd = (buddy == NULL) ? NULL : (qq_buddy_data *) buddy->proto_data;
 	if (bd != NULL) {
 		bd->client_tag = im_header->version_from;
+		bd->face = im_text.sender_icon;
+		qq_update_buddy_icon(gc->account, who, bd->face);
 	}
 
 	purple_msg_type = 0;
 
-	msg_with_purple_smiley = qq_smiley_to_purple(im_text.msg);
-	msg_utf8_encoded = im_text.font_attr ?
-	    qq_encode_to_purple(im_text.font_attr,
-			      im_text.font_attr_len,
-			      msg_with_purple_smiley, qd->client_version)
-		: qq_to_utf8(msg_with_purple_smiley, QQ_CHARSET_DEFAULT);
+	msg_smiley = qq_emoticon_to_purple(im_text.msg);
+	if (fmt != NULL) {
+		msg_fmt = qq_im_fmt_to_purple(fmt, msg_smiley);
+		msg_utf8 =  qq_to_utf8(msg_fmt, QQ_CHARSET_DEFAULT);
+		g_free(msg_fmt);
+		qq_im_fmt_free(fmt);
+	} else {
+		msg_utf8 =  qq_to_utf8(msg_smiley, QQ_CHARSET_DEFAULT);
+	}
+	g_free(msg_smiley);
 
 	/* send encoded to purple, note that we use im_text.send_time,
 	 * not the time we receive the message
 	 * as it may have been delayed when I am not online. */
-	serv_got_im(gc, who, msg_utf8_encoded, purple_msg_type, (time_t) im_text.send_time);
+	serv_got_im(gc, who, msg_utf8, purple_msg_type, (time_t) im_text.send_time);
 
-	g_free(msg_utf8_encoded);
-	g_free(msg_with_purple_smiley);
+	g_free(msg_utf8);
 	g_free(who);
 	g_free(im_text.msg);
-	if (im_text.font_attr) g_free(im_text.font_attr);
 }
 
 /* it is a normal IM, maybe text or video request */
@@ -400,7 +931,7 @@
 		return;
 	}
 	purple_debug_info("QQ",
-			"Got IM to %d, type: %02X from: %d ver: %s (%04X)\n",
+			"Got IM to %u, type: %02X from: %u ver: %s (%04X)\n",
 			im_header.uid_to, im_header.im_type, im_header.uid_from,
 			qq_get_ver_desc(im_header.version_from), im_header.version_from);
 
@@ -461,105 +992,63 @@
 		return;
 	}
 	purple_debug_info("QQ",
-			"Got Extend IM to %d, type: %02X from: %d ver: %s (%04X)\n",
+			"Got Extend IM to %u, type: %02X from: %u ver: %s (%04X)\n",
 			im_header.uid_to, im_header.im_type, im_header.uid_from,
 			qq_get_ver_desc(im_header.version_from), im_header.version_from);
 
 	switch (im_header.im_type) {
-	case QQ_NORMAL_IM_TEXT:
-		process_extend_im_text(gc, data + bytes, len - bytes, &im_header);
-		break;
-	case QQ_NORMAL_IM_FILE_REJECT_UDP:
-		qq_process_recv_file_reject (data + bytes, len - bytes, im_header.uid_from, gc);
-		break;
-	case QQ_NORMAL_IM_FILE_APPROVE_UDP:
-		qq_process_recv_file_accept (data + bytes, len - bytes, im_header.uid_from, gc);
-		break;
-	case QQ_NORMAL_IM_FILE_REQUEST_UDP:
-		qq_process_recv_file_request (data + bytes, len - bytes, im_header.uid_from, gc);
-		break;
-	case QQ_NORMAL_IM_FILE_CANCEL:
-		qq_process_recv_file_cancel (data + bytes, len - bytes, im_header.uid_from, gc);
-		break;
-	case QQ_NORMAL_IM_FILE_NOTIFY:
-		qq_process_recv_file_notify (data + bytes, len - bytes, im_header.uid_from, gc);
-		break;
-	default:
-		/* a simple process here, maybe more later */
-		qq_show_packet ("Unknow", data + bytes, len - bytes);
-		break;
+		case QQ_NORMAL_IM_TEXT:
+			process_extend_im_text(gc, data + bytes, len - bytes, &im_header);
+			break;
+		case QQ_NORMAL_IM_FILE_REJECT_UDP:
+			qq_process_recv_file_reject (data + bytes, len - bytes, im_header.uid_from, gc);
+			break;
+		case QQ_NORMAL_IM_FILE_APPROVE_UDP:
+			qq_process_recv_file_accept (data + bytes, len - bytes, im_header.uid_from, gc);
+			break;
+		case QQ_NORMAL_IM_FILE_REQUEST_UDP:
+			qq_process_recv_file_request (data + bytes, len - bytes, im_header.uid_from, gc);
+			break;
+		case QQ_NORMAL_IM_FILE_CANCEL:
+			qq_process_recv_file_cancel (data + bytes, len - bytes, im_header.uid_from, gc);
+			break;
+		case QQ_NORMAL_IM_FILE_NOTIFY:
+			qq_process_recv_file_notify (data + bytes, len - bytes, im_header.uid_from, gc);
+			break;
+		case QQ_NORMAL_IM_FILE_REQUEST_TCP:
+			/* Check ReceivedFileIM::parseContents in eva*/
+			/* some client use this function for detect invisable buddy*/
+		case QQ_NORMAL_IM_FILE_APPROVE_TCP:
+		case QQ_NORMAL_IM_FILE_REJECT_TCP:
+		case QQ_NORMAL_IM_FILE_PASV:
+		case QQ_NORMAL_IM_FILE_EX_REQUEST_UDP:
+		case QQ_NORMAL_IM_FILE_EX_REQUEST_ACCEPT:
+		case QQ_NORMAL_IM_FILE_EX_REQUEST_CANCEL:
+		case QQ_NORMAL_IM_FILE_EX_NOTIFY_IP:
+			qq_show_packet ("Not support", data, len);
+			break;
+		default:
+			/* a simple process here, maybe more later */
+			qq_show_packet ("Unknow", data + bytes, len - bytes);
+			break;
 	}
 }
 
 /* send an IM to uid_to */
-void qq_request_send_im(PurpleConnection *gc, guint32 uid_to, gchar *msg, gint type)
+static void request_send_im(PurpleConnection *gc, guint32 uid_to, gint type,
+	qq_im_format *fmt, gchar *msg, guint8 id, guint8 frag_count, guint8 frag_index)
 {
 	qq_data *qd;
-	guint8 *raw_data, *send_im_tail;
+	guint8 raw_data[MAX_PACKET_SIZE - 16];
 	guint16 im_type;
-	gint msg_len, raw_len, font_name_len, tail_len, bytes;
+	gint bytes;
 	time_t now;
-	gchar *msg_filtered;
-	GData *attribs;
-	gchar *font_size = NULL, *font_color = NULL, *font_name = NULL, *tmp;
-	gboolean is_bold = FALSE, is_italic = FALSE, is_underline = FALSE;
-	const gchar *start, *end, *last;
 
 	qd = (qq_data *) gc->proto_data;
 	im_type = QQ_NORMAL_IM_TEXT;
 
-	last = msg;
-	while (purple_markup_find_tag("font", last, &start, &end, &attribs)) {
-		tmp = g_datalist_get_data(&attribs, "size");
-		if (tmp) {
-			if (font_size)
-				g_free(font_size);
-			font_size = g_strdup(tmp);
-		}
-		tmp = g_datalist_get_data(&attribs, "color");
-		if (tmp) {
-			if (font_color)
-				g_free(font_color);
-			font_color = g_strdup(tmp);
-		}
-		tmp = g_datalist_get_data(&attribs, "face");
-		if (tmp) {
-			if (font_name)
-				g_free(font_name);
-			font_name = g_strdup(tmp);
-		}
-
-		g_datalist_clear(&attribs);
-		last = end + 1;
-	}
-
-	if (purple_markup_find_tag("b", msg, &start, &end, &attribs)) {
-		is_bold = TRUE;
-		g_datalist_clear(&attribs);
-	}
-
-	if (purple_markup_find_tag("i", msg, &start, &end, &attribs)) {
-		is_italic = TRUE;
-		g_datalist_clear(&attribs);
-	}
-
-	if (purple_markup_find_tag("u", msg, &start, &end, &attribs)) {
-		is_underline = TRUE;
-		g_datalist_clear(&attribs);
-	}
-
-	purple_debug_info("QQ_MESG", "send mesg: %s\n", msg);
-	msg_filtered = purple_markup_strip_html(msg);
-	msg_len = strlen(msg_filtered);
-	now = time(NULL);
-
-	font_name_len = (font_name) ? strlen(font_name) : DEFAULT_FONT_NAME_LEN;
-	tail_len = font_name_len + QQ_SEND_IM_AFTER_MSG_HEADER_LEN + 1;
-
-	raw_len = QQ_SEND_IM_BEFORE_MSG_LEN + msg_len + tail_len;
-	raw_data = g_newa(guint8, raw_len);
+	/* purple_debug_info("QQ", "Send IM %d-%d\n", frag_count, frag_index); */
 	bytes = 0;
-
 	/* 000-003: receiver uid */
 	bytes += qq_put32(raw_data + bytes, qd->uid);
 	/* 004-007: sender uid */
@@ -573,44 +1062,256 @@
 	/* 018-033: md5 of (uid+session_key) */
 	bytes += qq_putdata(raw_data + bytes, qd->session_md5, 16);
 	/* 034-035: message type */
-	bytes += qq_put16(raw_data + bytes, im_type);
+	bytes += qq_put16(raw_data + bytes, QQ_NORMAL_IM_TEXT);
 	/* 036-037: sequence number */
 	bytes += qq_put16(raw_data + bytes, qd->send_seq);
 	/* 038-041: send time */
+	now = time(NULL);
 	bytes += qq_put32(raw_data + bytes, (guint32) now);
 	/* 042-043: sender icon */
 	bytes += qq_put16(raw_data + bytes, qd->my_icon);
 	/* 044-046: always 0x00 */
 	bytes += qq_put16(raw_data + bytes, 0x0000);
 	bytes += qq_put8(raw_data + bytes, 0x00);
-	/* 047-047: we use font attr */
+	/* 047-047: always use font attr */
 	bytes += qq_put8(raw_data + bytes, 0x01);
 	/* 048-051: always 0x00 */
-	bytes += qq_put32(raw_data + bytes, 0x00000000);
+	/* Fixme: frag_count, frag_index not working now */
+	bytes += qq_put8(raw_data + bytes, frag_count);
+	bytes += qq_put8(raw_data + bytes, frag_index);
+	bytes += qq_put8(raw_data + bytes, id);
+	bytes += qq_put8(raw_data + bytes, 0);
 	/* 052-052: text message type (normal/auto-reply) */
 	bytes += qq_put8(raw_data + bytes, type);
 	/* 053-   : msg ends with 0x00 */
-	bytes += qq_putdata(raw_data + bytes, (guint8 *) msg_filtered, msg_len);
-	send_im_tail = qq_get_send_im_tail(font_color, font_size, font_name, is_bold,
-			is_italic, is_underline, tail_len);
-	/* qq_show_packet("qq_get_send_im_tail", send_im_tail, tail_len); */
-	bytes += qq_putdata(raw_data + bytes, send_im_tail, tail_len);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)msg, strlen(msg));
+	if (frag_count == frag_index + 1) {
+		bytes += qq_put8(raw_data + bytes, 0x20);	/* add extra SPACE */
+	}
+	bytes += qq_put_im_tail(raw_data + bytes, fmt);
+
+	/* qq_show_packet("QQ_CMD_SEND_IM", raw_data, bytes); */
+	qq_send_cmd(gc, QQ_CMD_SEND_IM, raw_data, bytes);
+}
+
+static void im_convert_and_merge(GString *dest, GString *append)
+{
+	gchar *converted;
+	g_return_if_fail(dest != NULL && append != NULL);
 
-	/* qq_show_packet("QQ_CMD_SEND_IM, raw_data, bytes); */
+	if (append->str == NULL || append->len <= 0) {
+		return;
+	}
+	/* purple_debug_info("QQ", "Append:\n%s\n", append->str); */
+	converted = utf8_to_qq(append->str, QQ_CHARSET_DEFAULT);
+	g_string_append(dest, converted);
+	g_string_set_size(append, 0);
+	g_free(converted);
+}
+
+GSList *qq_im_get_segments(gchar *msg_stripped, gboolean is_smiley_none)
+{
+	GSList *string_list = NULL;
+	GString *new_string;
+	GString *append_utf8;
+	gchar *start, *p;
+	gint count, len;
+	qq_emoticon *emoticon;
+
+	g_return_val_if_fail(msg_stripped != NULL, NULL);
+
+	start = msg_stripped;
+	count = 0;
+	new_string = g_string_new("");
+	append_utf8 = g_string_new("");
+	while (*start) {
+		p = start;
 
-	if (bytes == raw_len)	/* create packet OK */
-		qq_send_cmd(gc, QQ_CMD_SEND_IM, raw_data, bytes);
-	else
-		purple_debug_error("QQ",
-				"Fail creating send_im packet, expect %d bytes, build %d bytes\n", raw_len, bytes);
+		/* Convert emoticon */
+		if (!is_smiley_none && *p == '/') {
+			if (new_string->len + append_utf8->len + 2 > QQ_MSG_IM_MAX) {
+				/* enough chars to send */
+				im_convert_and_merge(new_string, append_utf8);
+				string_list = g_slist_append(string_list, strdup(new_string->str));
+				g_string_set_size(new_string, 0);
+				continue;
+			}
+			emoticon = emoticon_find(p);
+			if (emoticon != NULL) {
+				purple_debug_info("QQ", "found emoticon %s as 0x%02X\n",
+						emoticon->name, emoticon->symbol);
+				/* QQ emoticon code prevent converting from utf8 to QQ charset
+				 * convert append_utf8 to QQ charset
+				 * merge the result to dest
+				 * append qq QQ emoticon code to dest */
+				im_convert_and_merge(new_string, append_utf8);
+				g_string_append_c(new_string, 0x14);
+				g_string_append_c(new_string, emoticon->symbol);
+				start += strlen(emoticon->name);
+				continue;
+			} else {
+				purple_debug_info("QQ", "Not found emoticon %.20s\n", p);
+			}
+		}
 
-	if (font_color)
-		g_free(font_color);
-	if (font_size)
-		g_free(font_size);
-	g_free(send_im_tail);
-	g_free(msg_filtered);
+		/* Get next char */
+		start = g_utf8_next_char(p);
+		len = start - p;
+		if (new_string->len + append_utf8->len + len > QQ_MSG_IM_MAX) {
+			/* enough chars to send */
+			im_convert_and_merge(new_string, append_utf8);
+			string_list = g_slist_append(string_list, strdup(new_string->str));
+			g_string_set_size(new_string, 0);
+		}
+		g_string_append_len(append_utf8, p, len);
+	}
+
+	if (new_string->len + append_utf8->len > 0) {
+		im_convert_and_merge(new_string, append_utf8);
+		string_list = g_slist_append(string_list, strdup(new_string->str));
+	}
+	g_string_free(new_string, TRUE);
+	g_string_free(append_utf8, TRUE);
+	return string_list;
+}
+
+gboolean qq_im_smiley_none(const gchar *msg)
+{
+	const gchar *start, *end, *last;
+	GData *attribs;
+	gchar *tmp;
+	gboolean ret = FALSE;
+
+	g_return_val_if_fail(msg != NULL, TRUE);
+
+	last = msg;
+	while (purple_markup_find_tag("font", last, &start, &end, &attribs)) {
+		tmp = g_datalist_get_data(&attribs, "sml");
+		if (tmp && strlen(tmp) > 0) {
+			if (strcmp(tmp, "none") == 0) {
+				ret = TRUE;
+				break;
+			}
+		}
+		g_datalist_clear(&attribs);
+		last = end + 1;
+	}
+	return ret;
 }
 
+/* Grab custom emote icons
+static GSList*  qq_grab_emoticons(const char *msg, const char*username)
+{
+	GSList *list;
+	GList *smileys;
+	PurpleSmiley *smiley;
+	const char *smiley_shortcut;
+	char *ptr;
+	int length;
+	PurpleStoredImage *img;
 
+	smileys = purple_smileys_get_all();
+	length = strlen(msg);
 
+	for (; smileys; smileys = g_list_delete_link(smileys, smileys)) {
+		smiley = smileys->data;
+		smiley_shortcut = purple_smiley_get_shortcut(smiley);
+		purple_debug_info("QQ", "Smiley shortcut [%s]\n", smiley_shortcut);
+
+		ptr = g_strstr_len(msg, length, smiley_shortcut);
+
+		if (!ptr)
+			continue;
+
+		purple_debug_info("QQ", "Found Smiley shortcut [%s]\n", smiley_shortcut);
+
+		img = purple_smiley_get_stored_image(smiley);
+
+		emoticon = g_new0(MsnEmoticon, 1);
+		emoticon->smile = g_strdup(purple_smiley_get_shortcut(smiley));
+		emoticon->obj = msn_object_new_from_image(img,
+				purple_imgstore_get_filename(img),
+				username, MSN_OBJECT_EMOTICON);
+
+ 		purple_imgstore_unref(img);
+		list = g_slist_prepend(list, emoticon);
+	}
+	return list;
+}
+*/
+
+gint qq_send_im(PurpleConnection *gc, const gchar *who, const gchar *what, PurpleMessageFlags flags)
+{
+	qq_data *qd;
+	guint32 uid_to;
+	gint type;
+	qq_im_format *fmt;
+	gchar *msg_stripped, *tmp;
+	GSList *segments, *it;
+	gint msg_len;
+	const gchar *start_invalid;
+	gboolean is_smiley_none;
+	guint8 frag_count, frag_index;
+	guint8 msg_id;
+
+	g_return_val_if_fail(NULL != gc && NULL != gc->proto_data, -1);
+	g_return_val_if_fail(who != NULL && what != NULL, -1);
+
+	qd = (qq_data *) gc->proto_data;
+	purple_debug_info("QQ", "Send IM to %s, len %d:\n%s\n", who, strlen(what), what);
+
+	uid_to = purple_name_to_uid(who);
+	if (uid_to == qd->uid) {
+		/* if msg is to myself, bypass the network */
+		serv_got_im(gc, who, what, flags, time(NULL));
+		return 1;
+	}
+
+	type = (flags == PURPLE_MESSAGE_AUTO_RESP ? QQ_IM_AUTO_REPLY : QQ_IM_TEXT);
+	/* qq_show_packet("IM UTF8", (guint8 *)what, strlen(what)); */
+
+	msg_stripped = purple_markup_strip_html(what);
+	g_return_val_if_fail(msg_stripped != NULL, -1);
+	/* qq_show_packet("IM Stripped", (guint8 *)what, strlen(what)); */
+
+	/* Check and valid utf8 string */
+	msg_len = strlen(msg_stripped);
+	g_return_val_if_fail(msg_len > 0, -1);
+	if (!g_utf8_validate(msg_stripped, msg_len, &start_invalid)) {
+		if (start_invalid > msg_stripped) {
+			tmp = g_strndup(msg_stripped, start_invalid - msg_stripped);
+			g_free(msg_stripped);
+			msg_stripped = g_strconcat(tmp, _("(Invalid UTF-8 string)"), NULL);
+			g_free(tmp);
+		} else {
+			g_free(msg_stripped);
+			msg_stripped = g_strdup(_("(Invalid UTF-8 string)"));
+		}
+	}
+
+	is_smiley_none = qq_im_smiley_none(what);
+	segments = qq_im_get_segments(msg_stripped, is_smiley_none);
+	g_free(msg_stripped);
+
+	if (segments == NULL) {
+		return -1;
+	}
+
+	qd->send_im_id++;
+	msg_id = (guint8)(qd->send_im_id && 0xFF);
+	fmt = qq_im_fmt_new_by_purple(what);
+	frag_count = g_slist_length(segments);
+	frag_index = 0;
+	for (it = segments; it; it = it->next) {
+		/*
+		request_send_im(gc, uid_to, type, fmt, (gchar *)it->data,
+			msg_id, frag_count, frag_index);
+		*/
+		request_send_im(gc, uid_to, type, fmt, (gchar *)it->data, 0, 0, 0);
+		g_free(it->data);
+		frag_index++;
+	}
+	g_slist_free(segments);
+	qq_im_fmt_free(fmt);
+	return 1;
+}
--- a/libpurple/protocols/qq/im.h	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/im.h	Thu Dec 11 06:24:14 2008 +0000
@@ -27,22 +27,13 @@
 
 #include <glib.h>
 #include "connection.h"
-#include "group.h"
-
-#define QQ_MSG_IM_MAX               500	/* max length of IM */
-#define QQ_SEND_IM_BEFORE_MSG_LEN   53
-#define QQ_SEND_IM_AFTER_MSG_LEN    13	/* there is one 0x00 at the end */
-
-enum {
-	QQ_IM_TEXT = 0x01,
-	QQ_IM_AUTO_REPLY = 0x02
-};
 
 enum {
 	QQ_MSG_TO_BUDDY = 0x0009,
 	QQ_MSG_TO_UNKNOWN = 0x000a,
+	QQ_MSG_SMS = 0x0014,	/* not sure */
 	QQ_MSG_NEWS = 0x0018,
-	QQ_MSG_UNKNOWN_QUN_IM = 0x0020,
+	QQ_MSG_QUN_IM_UNKNOWN = 0x0020,
 	QQ_MSG_ADD_TO_QUN = 0x0021,
 	QQ_MSG_DEL_FROM_QUN = 0x0022,
 	QQ_MSG_APPLY_ADD_TO_QUN = 0x0023,
@@ -57,15 +48,29 @@
 	QQ_MSG_EXTEND_85 = 0x0085,
 };
 
-void qq_got_attention(PurpleConnection *gc, const gchar *msg);
+typedef struct {
+	guint8 attr;
+	guint8 rgb[3];
+	guint16 charset;
+	gchar *font;		/* Attension: font may NULL. font name is in QQ charset */
+	guint8 font_len;
+} qq_im_format;
 
-guint8 *qq_get_send_im_tail(const gchar *font_color,
-		const gchar *font_size,
-		const gchar *font_name,
-		gboolean is_bold, gboolean is_italic, gboolean is_underline, gint len);
+gint qq_put_im_tail(guint8 *buf, qq_im_format *fmt);
+gint qq_get_im_tail(qq_im_format *fmt, guint8 *data, gint data_len);
 
-void qq_request_send_im(PurpleConnection *gc, guint32 uid_to, gchar *msg, gint type);
+qq_im_format *qq_im_fmt_new(void);
+void qq_im_fmt_free(qq_im_format *fmt);
+qq_im_format *qq_im_fmt_new_by_purple(const gchar *msg);
+gchar *qq_im_fmt_to_purple(qq_im_format *fmt, gchar *text);
+gboolean qq_im_smiley_none(const gchar *msg);
+GSList *qq_im_get_segments(gchar *msg_stripped, gboolean is_smiley_none);
+
+void qq_got_message(PurpleConnection *gc, const gchar *msg);
+gint qq_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, PurpleMessageFlags flags);
 
 void qq_process_im(PurpleConnection *gc, guint8 *data, gint len);
 void qq_process_extend_im(PurpleConnection *gc, guint8 *data, gint len);
+
+gchar *qq_emoticon_to_purple(gchar *text);
 #endif
--- a/libpurple/protocols/qq/packet_parse.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/packet_parse.c	Thu Dec 11 06:24:14 2008 +0000
@@ -32,11 +32,9 @@
 /* note:
  * 1, in these functions, 'b' stands for byte, 'w' stands for word, 'dw' stands for double word.
  * 2, we use '*cursor' and 'buf' as two addresses to calculate the length.
- * 3, change '0' to '1', if want to get more info about the packet parsing. */
+ * 3, change 'undef' to 'define' to get more info about the packet parsing. */
 
-#if 0
-#define PARSER_DEBUG
-#endif
+#undef PARSER_DEBUG
 
 /* read one byte from buf,
  * return the number of bytes read if succeeds, otherwise return -1 */
--- a/libpurple/protocols/qq/qq.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/qq.c	Thu Dec 11 06:24:14 2008 +0000
@@ -56,9 +56,7 @@
 #include "utils.h"
 #include "version.h"
 
-#ifndef OPENQ_VERSION
-#define OPENQ_VERSION           DISPLAY_VERSION
-#endif
+#define OPENQ_VERSION 		"0.3.2-p19" 
 
 static GList *server_list_build(gchar select)
 {
@@ -91,7 +89,7 @@
 	PurpleConnection *gc;
 	qq_data *qd;
 	PurpleProxyInfo *gpi;
-	const gchar *user_server;
+	const gchar *custom_server;
 
 	gc = purple_account_get_connection(account);
 	g_return_if_fail(gc != NULL  && gc->proto_data != NULL);
@@ -101,11 +99,14 @@
 
 	qd->use_tcp = purple_account_get_bool(account, "use_tcp", TRUE);
 
-	user_server = purple_account_get_string(account, "server", NULL);
-	purple_debug_info("QQ", "Select server '%s'\n", user_server);
-	if ( (user_server != NULL && strlen(user_server) > 0) && strcasecmp(user_server, "auto") != 0) {
-		qd->servers = g_list_append(qd->servers, g_strdup(user_server));
-		return;
+	custom_server = purple_account_get_string(account, "server", NULL);
+
+	if (custom_server != NULL) {
+		purple_debug_info("QQ", "Select server '%s'\n", custom_server);
+		if (*custom_server != '\0' && g_ascii_strcasecmp(custom_server, "auto") != 0) {
+			qd->servers = g_list_append(qd->servers, g_strdup(custom_server));
+			return;
+		}
 	}
 
 	if (qd->use_tcp) {
@@ -268,9 +269,9 @@
 	case QQ_BUDDY_ONLINE_INVISIBLE:
 		g_string_append(status, _("Invisible"));
 		break;
-	case QQ_BUDDY_ONLINE_BUSY:
-		g_string_append(status, _("Busy"));
-		break;
+	case QQ_BUDDY_ONLINE_BUSY:
+		g_string_append(status, _("Busy"));
+		break;
 	default:
 		g_string_printf(status, _("Unknown-%d"), bd->status);
 	}
@@ -416,9 +417,9 @@
 			"invisible", _("Invisible"), FALSE, TRUE, FALSE);
 	types = g_list_append(types, status);
 
-	status = purple_status_type_new_full(PURPLE_STATUS_UNAVAILABLE,
-			"busy", _("Busy"), TRUE, TRUE, FALSE);
-	types = g_list_append(types, status);
+	status = purple_status_type_new_full(PURPLE_STATUS_UNAVAILABLE,
+			"busy", _("Busy"), TRUE, TRUE, FALSE);
+	types = g_list_append(types, status);
 
 	status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE,
 			"offline", _("Offline"), FALSE, TRUE, FALSE);
@@ -439,110 +440,6 @@
 	qq_request_change_status(gc, 0);
 }
 
-static void qq_add_deny(PurpleConnection *gc, const char *who)
-{
-	qq_data *qd;
-	g_return_if_fail(NULL != gc && NULL != gc->proto_data);
-
-	qd = (qq_data *) gc->proto_data;
-	if (!qd->is_login)
-		return;
-
-	if (!who || who[0] == '\0')
-		return;
-
-	purple_debug_info("QQ", "Add deny for %s\n", who);
-}
-
-static void qq_rem_deny(PurpleConnection *gc, const char *who)
-{
-	qq_data *qd;
-	g_return_if_fail(NULL != gc && NULL != gc->proto_data);
-
-	qd = (qq_data *) gc->proto_data;
-	if (!qd->is_login)
-		return;
-
-	if (!who || who[0] == '\0')
-		return;
-
-	purple_debug_info("QQ", "Rem deny for %s\n", who);
-}
-
-static void qq_set_permit_deny(PurpleConnection *gc)
-{
-	PurpleAccount *account;
-	GSList *deny;
-
-	purple_debug_info("QQ", "Set permit deny\n");
-	account = purple_connection_get_account(gc);
-	switch (account->perm_deny)
-	{
-		case PURPLE_PRIVACY_ALLOW_ALL:
-			for (deny = account->deny; deny; deny = deny->next)
-				qq_rem_deny(gc, deny->data);
-			break;
-
-		case PURPLE_PRIVACY_ALLOW_BUDDYLIST:
-		case PURPLE_PRIVACY_ALLOW_USERS:
-		case PURPLE_PRIVACY_DENY_USERS:
-		case PURPLE_PRIVACY_DENY_ALL:
-			for (deny = account->deny; deny; deny = deny->next)
-				qq_add_deny(gc, deny->data);
-			break;
-	}
-}
-
-/* IMPORTANT: PurpleConvImFlags -> PurpleMessageFlags */
-/* send an instant msg to a buddy */
-static gint qq_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, PurpleMessageFlags flags)
-{
-	gint type, uid_to;
-	gchar *msg, *msg_with_qq_smiley;
-	qq_data *qd;
-
-	g_return_val_if_fail(who != NULL, -1);
-
-	qd = (qq_data *) gc->proto_data;
-
-	g_return_val_if_fail(strlen(message) <= QQ_MSG_IM_MAX, -E2BIG);
-
-	type = (flags == PURPLE_MESSAGE_AUTO_RESP ? QQ_IM_AUTO_REPLY : QQ_IM_TEXT);
-	uid_to = purple_name_to_uid(who);
-
-	/* if msg is to myself, bypass the network */
-	if (uid_to == qd->uid) {
-		serv_got_im(gc, who, message, flags, time(NULL));
-	} else {
-		msg = utf8_to_qq(message, QQ_CHARSET_DEFAULT);
-		msg_with_qq_smiley = purple_smiley_to_qq(msg);
-		qq_request_send_im(gc, uid_to, msg_with_qq_smiley, type);
-		g_free(msg);
-		g_free(msg_with_qq_smiley);
-	}
-
-	return 1;
-}
-
-/* send a chat msg to a QQ Qun */
-static int qq_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags)
-{
-	gchar *msg, *msg_with_qq_smiley;
-	guint32 room_id = id;
-
-	g_return_val_if_fail(message != NULL, -1);
-	g_return_val_if_fail(strlen(message) <= QQ_MSG_IM_MAX, -E2BIG);
-
-	purple_debug_info("QQ_MESG", "Send qun mesg in utf8: %s\n", message);
-	msg = utf8_to_qq(message, QQ_CHARSET_DEFAULT);
-	msg_with_qq_smiley = purple_smiley_to_qq(msg);
-	qq_request_room_send_im(gc, room_id, msg_with_qq_smiley);
-	g_free(msg);
-	g_free(msg_with_qq_smiley);
-
-	return 1;
-}
-
 /* send packet to get who's detailed information */
 static void qq_show_buddy_info(PurpleConnection *gc, const gchar *who)
 {
@@ -760,13 +657,14 @@
 	g_string_append(info, "khc(at)pidgin.im<br>\n");
 	g_string_append(info, "qulogic(at)pidgin.im<br>\n");
 	g_string_append(info, "rlaager(at)pidgin.im<br>\n");
+	g_string_append(info, "Huang Guan : http://home.xxsyzx.com<br>\n");
 	g_string_append(info, "OpenQ Google Group : http://groups.google.com/group/openq<br>\n");
 	g_string_append(info, "<br>\n");
 	g_string_append(info, _("<p><i>And, all the boys in the backroom...</i><br>\n"));
 	g_string_append(info, _("<i>Feel free to join us!</i> :)"));
 	g_string_append(info, "</body></html>");
 
-	title = g_strdup_printf(_("About OpenQ r%s"), OPENQ_VERSION);
+	title = g_strdup_printf(_("About OpenQ %s"), OPENQ_VERSION);
 	purple_notify_formatted(gc, title, title, NULL, info->str, NULL, NULL);
 
 	g_free(title);
@@ -805,7 +703,7 @@
 	g_return_if_fail(components != NULL);
 
 	num_str = g_hash_table_lookup(components, QQ_ROOM_KEY_INTERNAL_ID);
-	room_id = strtol(num_str, NULL, 10);
+	room_id = strtoul(num_str, NULL, 10);
 	g_return_if_fail(room_id != 0);
 
 	qq_room_quit(gc, room_id);
@@ -824,7 +722,7 @@
 	g_return_if_fail(components != NULL);
 
 	num_str = g_hash_table_lookup(components, QQ_ROOM_KEY_INTERNAL_ID);
-	room_id = strtol(num_str, NULL, 10);
+	room_id = strtoul(num_str, NULL, 10);
 	g_return_if_fail(room_id != 0);
 
 	qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_INFO, room_id, NULL, 0,
@@ -1037,26 +935,26 @@
 	qq_status_types,					/* away_states	*/
 	qq_blist_node_menu,			/* blist_node_menu */
 	qq_chat_info,						/* chat_info */
-	qq_chat_info_defaults,					/* chat_info_defaults */
-	qq_login,							/* open */
-	qq_close,						/* close */
-	qq_send_im,						/* send_im */
+	qq_chat_info_defaults,		/* chat_info_defaults */
+	qq_login,					/* open */
+	qq_close,					/* close */
+	qq_send_im,				/* send_im */
 	NULL,							/* set_info */
 	NULL,							/* send_typing	*/
-	qq_show_buddy_info,						/* get_info */
-	qq_change_status,						/* change status */
+	qq_show_buddy_info,		/* get_info */
+	qq_change_status,			/* change status */
 	NULL,							/* set_idle */
 	NULL,							/* change_passwd */
-	qq_add_buddy,						/* add_buddy */
+	qq_add_buddy,			/* add_buddy */
 	NULL,							/* add_buddies	*/
-	qq_remove_buddy,					/* remove_buddy */
+	qq_remove_buddy,		/* remove_buddy */
 	NULL,							/* remove_buddies */
 	NULL,							/* add_permit */
-	qq_add_deny,							/* add_deny */
+	NULL,							/* add_deny */
 	NULL,							/* rem_permit */
 	NULL,							/* rem_deny */
-	qq_set_permit_deny,			/* set_permit_deny */
-	qq_group_join,						/* join_chat */
+	NULL,							/* set_permit_deny */
+	qq_group_join,			/* join_chat */
 	NULL,							/* reject chat	invite */
 	NULL,							/* get_chat_name */
 	NULL,							/* chat_invite	*/
@@ -1075,11 +973,11 @@
 	NULL,							/* normalize */
 	qq_set_custom_icon,
 	NULL,							/* remove_group */
-	qq_get_chat_buddy_real_name,				/* get_cb_real_name */
+	qq_get_chat_buddy_real_name,		/* get_cb_real_name */
 	NULL,							/* set_chat_topic */
 	NULL,							/* find_blist_chat */
-	qq_roomlist_get_list,					/* roomlist_get_list */
-	qq_roomlist_cancel,					/* roomlist_cancel */
+	qq_roomlist_get_list,	/* roomlist_get_list */
+	qq_roomlist_cancel,		/* roomlist_cancel */
 	NULL,							/* roomlist_expand_category */
 	NULL,							/* can_receive_file */
 	NULL,							/* qq_send_file send_file */
@@ -1170,7 +1068,6 @@
 	option = purple_account_option_list_new(_("Select Server"), "server", server_kv_list);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
-//#ifdef DEBUG
 	kvp = g_new0(PurpleKeyValuePair, 1);
 	kvp->key = g_strdup(_("QQ2005"));
 	kvp->value = g_strdup("qq2005");
@@ -1188,7 +1085,6 @@
 
 	option = purple_account_option_list_new(_("Client Version"), "client_version", version_kv_list);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
-//#endif
 
 	option = purple_account_option_bool_new(_("Connect by TCP"), "use_tcp", TRUE);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
@@ -1208,7 +1104,7 @@
 	purple_prefs_add_none("/plugins/prpl/qq");
 	purple_prefs_add_bool("/plugins/prpl/qq/show_status_by_icon", TRUE);
 	purple_prefs_add_bool("/plugins/prpl/qq/show_fake_video", FALSE);
-	purple_prefs_add_bool("/plugins/prpl/qq/auto_popup_conversation", FALSE);
+	purple_prefs_add_bool("/plugins/prpl/qq/auto_popup_conversation", TRUE);
 	purple_prefs_add_bool("/plugins/prpl/qq/auto_get_authorize_info", TRUE);
 	purple_prefs_add_int("/plugins/prpl/qq/resend_interval", 3);
 	purple_prefs_add_int("/plugins/prpl/qq/resend_times", 10);
--- a/libpurple/protocols/qq/qq.h	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/qq.h	Thu Dec 11 06:24:14 2008 +0000
@@ -182,6 +182,8 @@
 
 	gboolean is_show_notice;
 	gboolean is_show_news;
+
+	guint16 send_im_id;		/* send IM sequence number */
 };
 
 #endif
--- a/libpurple/protocols/qq/qq_base.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/qq_base.c	Thu Dec 11 06:24:14 2008 +0000
@@ -68,10 +68,11 @@
 	qd = (qq_data *) gc->proto_data;
 	/* qq_show_packet("Login reply", data, len); */
 
-	if (len < 139) {
+	if (len < 148) {
+		qq_show_packet("Login reply OK, but length < 139", data, len);
 		purple_connection_error_reason(gc,
 				PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
-				_("Can not decrypt server reply"));
+				_("Cannot decrypt server reply"));
 		return QQ_LOGIN_REPLY_ERR;
 	}
 
@@ -82,7 +83,7 @@
 	purple_debug_info("QQ", "Got session_key\n");
 	bytes += qq_get32(&uid, data + bytes);
 	if (uid != qd->uid) {
-		purple_debug_warning("QQ", "My uid in login reply is %d, not %d\n", uid, qd->uid);
+		purple_debug_warning("QQ", "My uid in login reply is %u, not %u\n", uid, qd->uid);
 	}
 	bytes += qq_getIP(&qd->my_ip, data + bytes);
 	bytes += qq_get16(&qd->my_port, data + bytes);
@@ -137,8 +138,8 @@
 			tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec);
 	/* unknow 9 bytes, 0x(00 0a 00 0a 01 00 00 0e 10) */
 
-	if (len > 139) {
-		purple_debug_warning("QQ", "Login reply more than expected %d bytes, read %d bytes\n", 139, bytes);
+	if (len > 148) {
+		qq_show_packet("Login reply OK, but length > 139", data, len);
 	}
 	return QQ_LOGIN_REPLY_OK;
 }
@@ -159,7 +160,7 @@
 	if (len < 11) {
 		purple_connection_error_reason(gc,
 				PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
-				_("Can not decrypt get server reply"));
+				_("Cannot decrypt server reply"));
 		return QQ_LOGIN_REPLY_ERR;
 	}
 
@@ -330,7 +331,7 @@
 	if (bytes + token_len > buf_len) {
 		purple_debug_info("QQ", "Extra token data, %d %d\n", token_len, buf_len - bytes);
 	}
-	qq_show_packet("Got token", buf + bytes, buf_len - bytes);
+	/* qq_show_packet("Got token", buf + bytes, buf_len - bytes); */
 
 	if (qd->ld.token != NULL) {
 		g_free(qd->ld.token);
@@ -423,7 +424,7 @@
 			qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ", data, data_len,
 					">>> [default] decrypt and dump");
 			error = g_strdup_printf(
-						_("Unknow reply code when login (0x%02X)"),
+						_("Unknown reply code when login (0x%02X)"),
 						ret );
 			reason = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
 			break;
@@ -502,7 +503,7 @@
 	/* In fact, we can send whatever we like to server
 	 * with this command, server return the same result including
 	 * the amount of online QQ users, my ip and port */
-	uid_str = g_strdup_printf("%u", qd->uid);
+	uid_str = g_strdup_printf("%u", qd->uid);
 	bytes += qq_putdata(raw_data + bytes, (guint8 *)uid_str, strlen(uid_str));
 	qq_send_cmd(gc, QQ_CMD_KEEP_ALIVE, raw_data, bytes);
 
@@ -521,17 +522,17 @@
 
 	/* qq_show_packet("Keep alive reply packet", data, len); */
 
-	bytes = 0;
-	bytes += qq_get8(&ret, data + bytes);
-	bytes += qq_get32(&qd->online_total, data + bytes);
+	bytes = 0;
+	bytes += qq_get8(&ret, data + bytes);
+	bytes += qq_get32(&qd->online_total, data + bytes);
 	if(0 == qd->online_total) {
 		purple_connection_error_reason(gc,
 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 				_("Keep alive error"));
 	}
-
-	bytes += qq_getIP(&qd->my_ip, data + bytes);
-	bytes += qq_get16(&qd->my_port, data + bytes);
+
+	bytes += qq_getIP(&qd->my_ip, data + bytes);
+	bytes += qq_get16(&qd->my_port, data + bytes);
 	return TRUE;
 }
 
@@ -565,16 +566,16 @@
 
 	/* qq_show_packet("Keep alive reply packet", data, len); */
 
-	bytes = 0;
-	bytes += qq_get8(&ret, data + bytes);
-	bytes += qq_get32(&qd->online_total, data + bytes);
+	bytes = 0;
+	bytes += qq_get8(&ret, data + bytes);
+	bytes += qq_get32(&qd->online_total, data + bytes);
 	if(0 == qd->online_total) {
 		purple_connection_error_reason(gc,
 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 				_("Keep alive error"));
 	}
-
-	bytes += qq_getIP(&qd->my_ip, data + bytes);
+
+	bytes += qq_getIP(&qd->my_ip, data + bytes);
 	bytes += qq_get16(&qd->my_port, data + bytes);
 	/* skip 2 byytes, 0x(00 3c) */
 	bytes += 2;
@@ -871,8 +872,8 @@
 	purple_request_field_group_add_field(group, field);
 
 	purple_request_fields(account,
-		_("QQ Captcha Verifing"),
-		_("QQ Captcha Verifing"),
+		_("QQ Captcha Verifying"),
+		_("QQ Captcha Verifying"),
 		_("Enter the text from the image"),
 		fields,
 		_("OK"), G_CALLBACK(captcha_input_ok_cb),
@@ -1110,7 +1111,7 @@
 			qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ", data, data_len,
 					">>> [default] decrypt and dump");
 			error = g_strdup_printf(
-						_("Unknow reply code when checking password (0x%02X)"),
+						_("Unknown reply code when checking password (0x%02X)"),
 						ret );
 			reason = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
 			break;
@@ -1256,7 +1257,7 @@
 				/* Missing get server before login*/
 			default:
 				error = g_strdup_printf(
-						_("Unknow reply code when login (0x%02X):\n%s"),
+						_("Unknown reply code when login (0x%02X):\n%s"),
 						ret, msg_utf8);
 				break;
 		}
@@ -1279,7 +1280,7 @@
 
 	bytes += qq_get32(&uid, data + bytes);
 	if (uid != qd->uid) {
-		purple_debug_warning("QQ", "My uid in login reply is %d, not %d\n", uid, qd->uid);
+		purple_debug_warning("QQ", "My uid in login reply is %u, not %u\n", uid, qd->uid);
 	}
 	bytes += qq_getIP(&qd->my_ip, data + bytes);
 	bytes += qq_get16(&qd->my_port, data + bytes);
@@ -1445,7 +1446,7 @@
 				break;
 			default:
 				error = g_strdup_printf(
-						_("Unknow reply code when login (0x%02X):\n%s"),
+						_("Unknown reply code when login (0x%02X):\n%s"),
 						ret, msg_utf8);
 				break;
 		}
@@ -1468,7 +1469,7 @@
 
 	bytes += qq_get32(&uid, data + bytes);
 	if (uid != qd->uid) {
-		purple_debug_warning("QQ", "My uid in login reply is %d, not %d\n", uid, qd->uid);
+		purple_debug_warning("QQ", "My uid in login reply is %u, not %u\n", uid, qd->uid);
 	}
 	bytes += qq_getIP(&qd->my_ip, data + bytes);
 	bytes += qq_get16(&qd->my_port, data + bytes);
--- a/libpurple/protocols/qq/qq_define.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/qq_define.c	Thu Dec 11 06:24:14 2008 +0000
@@ -132,65 +132,65 @@
 	case QQ_CMD_LOGOUT:
 		return "QQ_CMD_LOGOUT";
 	case QQ_CMD_KEEP_ALIVE:
-		return "QQ_CMD_KEEP_ALIVE";
+		return "CMD_KEEP_ALIVE";
 	case QQ_CMD_UPDATE_INFO:
-		return "QQ_CMD_UPDATE_INFO";
+		return "CMD_UPDATE_INFO";
 	case QQ_CMD_SEARCH_USER:
-		return "QQ_CMD_SEARCH_USER";
+		return "CMD_SEARCH_USER";
 	case QQ_CMD_GET_BUDDY_INFO:
-		return "QQ_CMD_GET_BUDDY_INFO";
+		return "CMD_GET_BUDDY_INFO";
 	case QQ_CMD_ADD_BUDDY_NO_AUTH:
-		return "QQ_CMD_ADD_BUDDY_NO_AUTH";
+		return "CMD_ADD_BUDDY_NO_AUTH";
 	case QQ_CMD_REMOVE_BUDDY:
-		return "QQ_CMD_REMOVE_BUDDY";
+		return "CMD_REMOVE_BUDDY";
 	case QQ_CMD_ADD_BUDDY_AUTH:
-		return "QQ_CMD_ADD_BUDDY_AUTH";
+		return "CMD_ADD_BUDDY_AUTH";
 	case QQ_CMD_CHANGE_STATUS:
-		return "QQ_CMD_CHANGE_STATUS";
+		return "CMD_CHANGE_STATUS";
 	case QQ_CMD_ACK_SYS_MSG:
-		return "QQ_CMD_ACK_SYS_MSG";
+		return "CMD_ACK_SYS_MSG";
 	case QQ_CMD_SEND_IM:
-		return "QQ_CMD_SEND_IM";
+		return "CMD_SEND_IM";
 	case QQ_CMD_RECV_IM:
-		return "QQ_CMD_RECV_IM";
+		return "CMD_RECV_IM";
 	case QQ_CMD_REMOVE_ME:
-		return "QQ_CMD_REMOVE_ME";
+		return "CMD_REMOVE_ME";
 	case QQ_CMD_LOGIN:
-		return "QQ_CMD_LOGIN";
+		return "CMD_LOGIN";
 	case QQ_CMD_GET_BUDDIES_LIST:
-		return "QQ_CMD_GET_BUDDIES_LIST";
+		return "CMD_GET_BUDDIES_LIST";
 	case QQ_CMD_GET_BUDDIES_ONLINE:
-		return "QQ_CMD_GET_BUDDIES_ONLINE";
+		return "CMD_GET_BUDDIES_ONLINE";
 	case QQ_CMD_ROOM:
-		return "QQ_CMD_ROOM";
+		return "CMD_ROOM";
 	case QQ_CMD_GET_BUDDIES_AND_ROOMS:
-		return "QQ_CMD_GET_BUDDIES_AND_ROOMS";
+		return "CMD_GET_BUDDIES_AND_ROOMS";
 	case QQ_CMD_GET_LEVEL:
-		return "QQ_CMD_GET_LEVEL";
+		return "CMD_GET_LEVEL";
 	case QQ_CMD_TOKEN:
-		return "QQ_CMD_TOKEN";
+		return "CMD_TOKEN";
 	case QQ_CMD_RECV_MSG_SYS:
-		return "QQ_CMD_RECV_MSG_SYS";
+		return "CMD_RECV_MSG_SYS";
 	case QQ_CMD_BUDDY_CHANGE_STATUS:
-		return "QQ_CMD_BUDDY_CHANGE_STATUS";
+		return "CMD_BUDDY_CHANGE_STATUS";
 	case QQ_CMD_GET_SERVER:
-		return "QQ_CMD_GET_SERVER";
+		return "CMD_GET_SERVER";
 	case QQ_CMD_TOKEN_EX:
-		return "QQ_CMD_TOKEN_EX";
+		return "CMD_TOKEN_EX";
 	case QQ_CMD_CHECK_PWD:
-		return "QQ_CMD_CHECK_PWD";
+		return "CMD_CHECK_PWD";
 	case QQ_CMD_AUTH_CODE:
-		return "QQ_CMD_AUTH_CODE";
+		return "CMD_AUTH_CODE";
 	case QQ_CMD_ADD_BUDDY_NO_AUTH_EX:
-		return "QQ_CMD_ADD_BUDDY_NO_AUTH_EX";
+		return "CMD_ADD_BUDDY_NO_AUTH_EX";
 	case QQ_CMD_ADD_BUDDY_AUTH_EX:
-		return "QQ_CMD_BUDDY_ADD_AUTH_EX";
+		return "CMD_BUDDY_ADD_AUTH_EX";
 	case QQ_CMD_BUDDY_CHECK_CODE:
-		return "QQ_CMD_BUDDY_CHECK_CODE";
+		return "CMD_BUDDY_CHECK_CODE";
 	case QQ_CMD_BUDDY_QUESTION:
-		return "QQ_CMD_BUDDY_QUESTION";
+		return "CMD_BUDDY_QUESTION";
 	default:
-		return "Unknown CMD";
+		return "CMD_UNKNOW";
 	}
 }
 
@@ -198,55 +198,55 @@
 {
 	switch (room_cmd) {
 	case QQ_ROOM_CMD_CREATE:
-		return "QQ_ROOM_CMD_CREATE";
+		return "ROOM_CMD_CREATE";
 	case QQ_ROOM_CMD_MEMBER_OPT:
-		return "QQ_ROOM_CMD_MEMBER_OPT";
+		return "ROOM_CMD_MEMBER_OPT";
 	case QQ_ROOM_CMD_CHANGE_INFO:
-		return "QQ_ROOM_CMD_CHANGE_INFO";
+		return "ROOM_CMD_CHANGE_INFO";
 	case QQ_ROOM_CMD_GET_INFO:
-		return "QQ_ROOM_CMD_GET_INFO";
+		return "ROOM_CMD_GET_INFO";
 	case QQ_ROOM_CMD_ACTIVATE:
-		return "QQ_ROOM_CMD_ACTIVATE";
+		return "ROOM_CMD_ACTIVATE";
 	case QQ_ROOM_CMD_SEARCH:
-		return "QQ_ROOM_CMD_SEARCH";
+		return "ROOM_CMD_SEARCH";
 	case QQ_ROOM_CMD_JOIN:
-		return "QQ_ROOM_CMD_JOIN";
+		return "ROOM_CMD_JOIN";
 	case QQ_ROOM_CMD_AUTH:
-		return "QQ_ROOM_CMD_AUTH";
+		return "ROOM_CMD_AUTH";
 	case QQ_ROOM_CMD_QUIT:
-		return "QQ_ROOM_CMD_QUIT";
-	case QQ_ROOM_CMD_SEND_MSG:
-		return "QQ_ROOM_CMD_SEND_MSG";
+		return "ROOM_CMD_QUIT";
+	case QQ_ROOM_CMD_SEND_IM:
+		return "ROOM_CMD_SEND_IM";
 	case QQ_ROOM_CMD_GET_ONLINES:
-		return "QQ_ROOM_CMD_GET_ONLINES";
+		return "ROOM_CMD_GET_ONLINES";
 	case QQ_ROOM_CMD_GET_BUDDIES:
-		return "QQ_ROOM_CMD_GET_BUDDIES";
+		return "ROOM_CMD_GET_BUDDIES";
 	case QQ_ROOM_CMD_CHANGE_CARD:
-		return "QQ_ROOM_CMD_CHANGE_CARD";
+		return "ROOM_CMD_CHANGE_CARD";
 	case QQ_ROOM_CMD_GET_REALNAMES:
-		return "QQ_ROOM_CMD_GET_REALNAMES";
+		return "ROOM_CMD_GET_REALNAMES";
 	case QQ_ROOM_CMD_GET_CARD:
-		return "QQ_ROOM_CMD_GET_CARD";
+		return "ROOM_CMD_GET_CARD";
 	case QQ_ROOM_CMD_SEND_IM_EX:
-		return "QQ_ROOM_CMD_SEND_IM_EX";
+		return "ROOM_CMD_SEND_IM_EX";
 	case QQ_ROOM_CMD_ADMIN:
-		return "QQ_ROOM_CMD_ADMIN";
+		return "ROOM_CMD_ADMIN";
 	case QQ_ROOM_CMD_TRANSFER:
-		return "QQ_ROOM_CMD_TRANSFER";
+		return "ROOM_CMD_TRANSFER";
 	case QQ_ROOM_CMD_TEMP_CREATE:
-		return "QQ_ROOM_CMD_TEMP_CREATE";
+		return "ROOM_CMD_TEMP_CREATE";
 	case QQ_ROOM_CMD_TEMP_CHANGE_MEMBER:
-		return "QQ_ROOM_CMD_TEMP_CHANGE_MEMBER";
+		return "ROOM_CMD_TEMP_CHANGE_MEMBER";
 	case QQ_ROOM_CMD_TEMP_QUIT:
-		return "QQ_ROOM_CMD_TEMP_QUIT";
+		return "ROOM_CMD_TEMP_QUIT";
 	case QQ_ROOM_CMD_TEMP_GET_INFO:
-		return "QQ_ROOM_CMD_TEMP_GET_INFO";
+		return "ROOM_CMD_TEMP_GET_INFO";
 	case QQ_ROOM_CMD_TEMP_SEND_IM:
-		return "QQ_ROOM_CMD_TEMP_SEND_IM";
+		return "ROOM_CMD_TEMP_SEND_IM";
 	case QQ_ROOM_CMD_TEMP_GET_MEMBERS:
-		return "QQ_ROOM_CMD_TEMP_GET_MEMBERS";
+		return "ROOM_CMD_TEMP_GET_MEMBERS";
 	default:
-		return "Unknown Room Command";
+		return "ROOM_CMD_UNKNOW";
 	}
 }
 
--- a/libpurple/protocols/qq/qq_define.h	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/qq_define.h	Thu Dec 11 06:24:14 2008 +0000
@@ -89,7 +89,7 @@
 	QQ_ROOM_CMD_JOIN = 0x07,
 	QQ_ROOM_CMD_AUTH = 0x08,
 	QQ_ROOM_CMD_QUIT = 0x09,
-	QQ_ROOM_CMD_SEND_MSG = 0x0a,
+	QQ_ROOM_CMD_SEND_IM = 0x0a,
 	QQ_ROOM_CMD_GET_ONLINES = 0x0b,
 	QQ_ROOM_CMD_GET_BUDDIES = 0x0c,
 
--- a/libpurple/protocols/qq/qq_network.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/qq_network.c	Thu Dec 11 06:24:14 2008 +0000
@@ -302,8 +302,7 @@
 	update_class = qq_trans_get_class(trans);
 	ship32 = qq_trans_get_ship(trans);
 	if (update_class != 0 || ship32 != 0) {
-		purple_debug_info("QQ", "Process in Update class %d, ship32 %d\n",
-				update_class, ship32);
+		purple_debug_info("QQ", "Update class %d, ship32 %d\n", update_class, ship32);
 	}
 
 	switch (cmd) {
@@ -323,10 +322,6 @@
 		case QQ_CMD_ROOM:
 			room_cmd = qq_trans_get_room_cmd(trans);
 			room_id = qq_trans_get_room_id(trans);
-#if 1
-			purple_debug_info("QQ", "%s (0x%02X) for room %d, len %d\n",
-					qq_get_room_cmd_desc(room_cmd), room_cmd, room_id, buf_len);
-#endif
 			qq_proc_room_cmds(gc, seq, room_cmd, room_id, buf + bytes, bytes_not_read, update_class, ship32);
 			break;
 		default:
@@ -381,7 +376,7 @@
 			/* No worries */
 			return;
 
-		error_msg = g_strdup_printf(_("Lost connection with server:\n%d, %s"), errno, g_strerror(errno));
+		error_msg = g_strdup_printf(_("Lost connection with server: %d, %s"), errno, g_strerror(errno));
 		purple_connection_error_reason(gc,
 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 				error_msg);
@@ -630,11 +625,13 @@
 	}
 
 	if (ret < data_len) {
-		purple_debug_info("TCP_SEND_OUT",
-			"Add %d bytes to buffer\n", data_len - ret);
+		purple_debug_info("TCP_SEND_OUT", "Add %d bytes to buffer\n", data_len - ret);
 		if (conn->can_write_handler == 0) {
 			conn->can_write_handler = purple_input_add(qd->fd, PURPLE_INPUT_WRITE, tcp_can_write, gc);
 		}
+		if (conn->tcp_txbuf == NULL) {
+			conn->tcp_txbuf = purple_circ_buffer_new(4096);
+		}
 		purple_circ_buffer_append(conn->tcp_txbuf, data + ret, data_len - ret);
 	}
 	return ret;
@@ -707,7 +704,7 @@
 	qd->send_seq = rand() & 0xffff;
 
 	qd->is_login = FALSE;
-	qd->uid = strtol(purple_account_get_username(purple_connection_get_account(gc)), NULL, 10);
+	qd->uid = strtoul(purple_account_get_username(purple_connection_get_account(gc)), NULL, 10);
 
 #ifdef DEBUG
 	memset(qd->ld.random_key, 0x01, sizeof(qd->ld.random_key));
@@ -938,7 +935,7 @@
 		return FALSE;
 	}
 
-	purple_connection_update_progress(gc, _("Connecting server ..."), 1, QQ_CONNECT_STEPS);
+	purple_connection_update_progress(gc, _("Connecting to server ..."), 1, QQ_CONNECT_STEPS);
 
 	purple_debug_info("QQ", "Connect to %s:%d\n", server, port);
 
@@ -954,7 +951,7 @@
 		qd->conn_data = purple_proxy_connect_udp(gc, account, server, port, connect_cb, gc);
 	}
 	if ( qd->conn_data == NULL ) {
-		purple_debug_error("QQ", _("Couldn't create socket"));
+		purple_debug_error("QQ", "Couldn't create socket");
 		return FALSE;
 	}
 #else
@@ -1107,7 +1104,7 @@
 
 #if 1
 		/* qq_show_packet("qq_send_cmd_encrypted", data, data_len); */
-		purple_debug_info("QQ", "<== [%05d], %s(0x%04X), datalen %d\n",
+		purple_debug_info("QQ", "<== [%05d] %s(0x%04X), datalen %d\n",
 				seq, qq_get_cmd_desc(cmd), cmd, encrypted_len);
 #endif
 
@@ -1161,7 +1158,7 @@
 
 	seq = ++qd->send_seq;
 #if 1
-		purple_debug_info("QQ", "<== [%05d], %s(0x%04X), datalen %d\n",
+		purple_debug_info("QQ", "<== [%05d] %s(0x%04X), datalen %d\n",
 				seq, qq_get_cmd_desc(cmd), cmd, data_len);
 #endif
 	return send_cmd_detail(gc, cmd, seq, data, data_len, TRUE, update_class, ship32);
@@ -1186,7 +1183,7 @@
 		is_save2trans = FALSE;
 	}
 #if 1
-		purple_debug_info("QQ", "<== [%05d], %s(0x%04X), datalen %d\n",
+		purple_debug_info("QQ", "<== [%05d] %s(0x%04X), datalen %d\n",
 				seq, qq_get_cmd_desc(cmd), cmd, data_len);
 #endif
 	return send_cmd_detail(gc, cmd, seq, data, data_len, is_save2trans, 0, 0);
@@ -1205,7 +1202,7 @@
 	g_return_val_if_fail(data != NULL && data_len > 0, -1);
 
 #if 1
-		purple_debug_info("QQ", "<== [SRV-%05d], %s(0x%04X), datalen %d\n",
+		purple_debug_info("QQ", "<== [SRV-%05d] %s(0x%04X), datalen %d\n",
 				seq, qq_get_cmd_desc(cmd), cmd, data_len);
 #endif
 	/* at most 16 bytes more */
@@ -1244,7 +1241,7 @@
 	buf_len = 0;
 	buf_len += qq_put8(buf + buf_len, room_cmd);
 	if (room_id != 0) {
-		/* id 0 is for QQ Demo Group, now there are not existed*/
+		/* id 0 is for QQ Demo Group, now they are closed*/
 		buf_len += qq_put32(buf + buf_len, room_id);
 	}
 	if (data != NULL && data_len > 0) {
@@ -1268,7 +1265,7 @@
 #if 1
 		/* qq_show_packet("send_room_cmd", buf, buf_len); */
 		purple_debug_info("QQ",
-				"<== [%05d], %s (0x%02X) to room %d, datalen %d\n",
+				"<== [%05d] %s (0x%02X) to room %d, datalen %d\n",
 				seq, qq_get_room_cmd_desc(room_cmd), room_cmd, room_id, buf_len);
 #endif
 
--- a/libpurple/protocols/qq/qq_process.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/qq_process.c	Thu Dec 11 06:24:14 2008 +0000
@@ -94,7 +94,7 @@
 	purple_debug_info("QQ", "OK sent IM\n");
 }
 
-static void do_server_news(guint8 *data, gint data_len, PurpleConnection *gc)
+static void do_server_news(PurpleConnection *gc, guint8 *data, gint data_len)
 {
 	qq_data *qd = (qq_data *) gc->proto_data;
 	gint bytes;
@@ -114,7 +114,7 @@
 	content = g_strdup_printf(_("Server News:\n%s\n%s\n%s"), title, brief, url);
 
 	if (qd->is_show_news) {
-		qq_got_attention(gc, content);
+		qq_got_message(gc, content);
 	} else {
 		purple_debug_info("QQ", "QQ Server news:\n%s\n", content);
 	}
@@ -124,6 +124,40 @@
 	g_free(content);
 }
 
+static void do_got_sms(PurpleConnection *gc, guint8 *data, gint data_len)
+{
+	gint bytes;
+	gchar *mobile = NULL;
+	gchar *msg = NULL;
+	gchar *msg_utf8 = NULL;
+	gchar *msg_formated;
+
+	g_return_if_fail(data != NULL && data_len > 26);
+
+	qq_show_packet("Rcv sms", data, data_len);
+
+	bytes = 0;
+	bytes += 1;	/* skip 0x00 */
+	mobile = g_strndup((gchar *)data + bytes, 20);
+	bytes += 20;
+	bytes += 5; /* skip 0x(49 11 98 d5 03)*/
+	if (bytes < data_len) {
+		msg = g_strndup((gchar *)data + bytes, data_len - bytes);
+		msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT);
+		g_free(msg);
+	} else {
+		msg_utf8 = g_strdup("");
+	}
+
+	msg_formated = g_strdup_printf(_("%s:%s"), mobile, msg_utf8);
+
+	qq_got_message(gc, msg_formated);
+
+	g_free(msg_formated);
+	g_free(msg_utf8);
+	g_free(mobile);
+}
+
 static void do_msg_sys_30(PurpleConnection *gc, guint8 *data, gint data_len)
 {
 	gint len;
@@ -142,7 +176,7 @@
 		purple_debug_warning("QQ", "We are kicked out by QQ server\n");
 
 	msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT);
-	qq_got_attention(gc, msg_utf8);
+	qq_got_message(gc, msg_utf8);
 }
 
 static void do_msg_sys_4c(PurpleConnection *gc, guint8 *data, gint data_len)
@@ -172,7 +206,7 @@
 		purple_debug_warning("QQ", "Failed to read QQ_MSG_SYS_4C\n");
 		qq_show_packet("do_msg_sys_4c", data, data_len);
 	}
-	qq_got_attention(gc, content->str);
+	qq_got_message(gc, content->str);
 	g_string_free(content, FALSE);
 }
 
@@ -183,8 +217,8 @@
 			return "QQ_MSG_TO_BUDDY";
 		case QQ_MSG_TO_UNKNOWN:
 			return "QQ_MSG_TO_UNKNOWN";
-		case QQ_MSG_UNKNOWN_QUN_IM:
-			return "QQ_MSG_UNKNOWN_QUN_IM";
+		case QQ_MSG_QUN_IM_UNKNOWN:
+			return "QQ_MSG_QUN_IM_UNKNOWN";
 		case QQ_MSG_ADD_TO_QUN:
 			return "QQ_MSG_ADD_TO_QUN";
 		case QQ_MSG_DEL_FROM_QUN:
@@ -207,6 +241,8 @@
 			return "QQ_MSG_QUN_IM";
 		case QQ_MSG_NEWS:
 			return "QQ_MSG_NEWS";
+		case QQ_MSG_SMS:
+			return "QQ_MSG_SMS";
 		case QQ_MSG_EXTEND:
 			return "QQ_MSG_EXTEND";
 		case QQ_MSG_EXTEND_85:
@@ -262,7 +298,7 @@
 	/* im_header prepared */
 
 	if (header.uid_to != qd->uid) {	/* should not happen */
-		purple_debug_error("QQ", "MSG to [%d], NOT me\n", header.uid_to);
+		purple_debug_error("QQ", "MSG to %u, NOT me\n", header.uid_to);
 		return;
 	}
 
@@ -274,7 +310,10 @@
 
 	switch (header.msg_type) {
 		case QQ_MSG_NEWS:
-			do_server_news(data + bytes, data_len - bytes, gc);
+			do_server_news(gc, data + bytes, data_len - bytes);
+			break;
+		case QQ_MSG_SMS:
+			do_got_sms(gc, data + bytes, data_len - bytes);
 			break;
 		case QQ_MSG_EXTEND:
 		case QQ_MSG_EXTEND_85:
@@ -286,7 +325,7 @@
 			purple_debug_info("QQ", "MSG from buddy [%d]\n", header.uid_from);
 			qq_process_im(gc, data + bytes, data_len - bytes);
 			break;
-		case QQ_MSG_UNKNOWN_QUN_IM:
+		case QQ_MSG_QUN_IM_UNKNOWN:
 		case QQ_MSG_TEMP_QUN_IM:
 		case QQ_MSG_QUN_IM:
 			purple_debug_info("QQ", "MSG from room [%d]\n", header.uid_from);
@@ -327,9 +366,12 @@
 			do_msg_sys_4c(gc, data + bytes, data_len - bytes);
 			break;
 		default:
-			purple_debug_warning("QQ", "MSG from [%d], unknown type %s [0x%04X]\n",
+			purple_debug_warning("QQ", "MSG from %u, unknown type %s [0x%04X]\n",
 					header.uid_from, get_im_type_desc(header.msg_type), header.msg_type);
-			qq_show_packet("Unknown MSG type", data, data_len);
+			qq_show_packet("MSG header", data, bytes);
+			if (data_len - bytes > 0) {
+				qq_show_packet("MSG data", data + bytes, data_len - bytes);
+			}
 			break;
 	}
 }
@@ -381,7 +423,7 @@
 	content = g_strdup_printf(_("Server notice From %s: \n%s"), from, msg_utf8);
 
 	if (qd->is_show_notice) {
-		qq_got_attention(gc, content);
+		qq_got_message(gc, content);
 	} else {
 		purple_debug_info("QQ", "QQ Server notice from %s:\n%s", from, msg_utf8);
 	}
@@ -425,7 +467,7 @@
 	request_server_ack(gc, funct_str, from, seq);
 
 	/* qq_show_packet("Server MSG", data, data_len); */
-	if (strtol(to, NULL, 10) != qd->uid) {	/* not to me */
+	if (strtoul(to, NULL, 10) != qd->uid) {	/* not to me */
 		purple_debug_error("QQ", "Recv sys msg to [%s], not me!, discard\n", to);
 		g_strfreev(segments);
 		return;
@@ -496,7 +538,7 @@
 			qq_process_buddy_change_status(data, data_len, gc);
 			break;
 		default:
-			process_unknow_cmd(gc, _("Unknow SERVER CMD"), data, data_len, cmd, seq);
+			process_unknow_cmd(gc, _("Unknown SERVER CMD"), data, data_len, cmd, seq);
 			break;
 	}
 }
@@ -512,7 +554,7 @@
 	msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT);
 	g_free(msg);
 
-	prim = g_strdup_printf(_("Error reply of %s(0x%02X)\nRoom %d, reply 0x%02X"),
+	prim = g_strdup_printf(_("Error reply of %s(0x%02X)\nRoom %u, reply 0x%02X"),
 		qq_get_room_cmd_desc(room_cmd), room_cmd, room_id, reply);
 
 	purple_notify_error(gc, _("QQ Qun Command"), prim, msg_utf8);
@@ -562,13 +604,13 @@
 	qd = (qq_data *) gc->proto_data;
 
 	next_id = qq_room_get_next(gc, room_id);
-	purple_debug_info("QQ", "Update rooms, next id %d, prev id %d\n", next_id, room_id);
+	purple_debug_info("QQ", "Update rooms, next id %u, prev id %u\n", next_id, room_id);
 
 	if (next_id <= 0) {
 		if (room_id > 0) {
 			is_new_turn = TRUE;
 			next_id = qq_room_get_next(gc, 0);
-			purple_debug_info("QQ", "new turn, id %d\n", next_id);
+			purple_debug_info("QQ", "New turn, id %u\n", next_id);
 		} else {
 			purple_debug_info("QQ", "No room. Finished update\n");
 			return;
@@ -754,7 +796,7 @@
 						seq, room_cmd, qq_get_room_cmd_desc(room_cmd), room_id, rcved_len);
 			} else {
 				purple_debug_warning("QQ",
-					   _("Not a member of room \"%s\"\n"), rmd->title_utf8);
+					   "Not a member of room \"%s\"\n", rmd->title_utf8);
 				rmd->my_role = QQ_ROOM_ROLE_NO;
 			}
 			break;
@@ -798,9 +840,12 @@
 	case QQ_ROOM_CMD_QUIT:
 		qq_process_group_cmd_exit_group(data + bytes, data_len - bytes, gc);
 		break;
-	case QQ_ROOM_CMD_SEND_MSG:
+	case QQ_ROOM_CMD_SEND_IM:
 		qq_process_room_send_im(gc, data + bytes, data_len - bytes);
 		break;
+	case QQ_ROOM_CMD_SEND_IM_EX:
+		qq_process_room_send_im_ex(gc, data + bytes, data_len - bytes);
+		break;
 	case QQ_ROOM_CMD_GET_ONLINES:
 		qq_process_room_cmd_get_onlines(data + bytes, data_len - bytes, gc);
 		break;
@@ -934,20 +979,20 @@
 			if (ret_8 != QQ_LOGIN_REPLY_OK) {
 				return ret_8;
 			}
-			if (qd->client_version == 2008) {
+			if (qd->client_version >= 2008) {
 				qq_request_login_2008(gc);
 			} else {
 				qq_request_login_2007(gc);
 			}
 			break;
 		case QQ_CMD_LOGIN:
-			if (qd->client_version == 2008) {
+			if (qd->client_version >= 2008) {
 				ret_8 = qq_process_login_2008(gc, data, data_len);
 				if ( ret_8 == QQ_LOGIN_REPLY_REDIRECT) {
                 		qq_request_get_server(gc);
                 		return QQ_LOGIN_REPLY_OK;
             	}
-			} else if (qd->client_version == 2007) {
+			} else if (qd->client_version >= 2007) {
 				ret_8 = qq_process_login_2007(gc, data, data_len);
 				if ( ret_8 == QQ_LOGIN_REPLY_REDIRECT) {
                 		qq_request_get_server(gc);
@@ -961,7 +1006,7 @@
 			}
 
 			purple_connection_update_progress(gc, _("Logging in"), QQ_CONNECT_STEPS - 1, QQ_CONNECT_STEPS);
-			purple_debug_info("QQ", "Login repliess OK; everything is fine\n");
+			purple_debug_info("QQ", "Login replies OK; everything is fine\n");
 			purple_connection_set_state(gc, PURPLE_CONNECTED);
 			qd->is_login = TRUE;	/* must be defined after sev_finish_login */
 
@@ -974,7 +1019,7 @@
 			qq_update_all(gc, 0);
 			break;
 		default:
-			process_unknow_cmd(gc, _("Unknow LOGIN CMD"), data, data_len, cmd, seq);
+			process_unknow_cmd(gc, _("Unknown LOGIN CMD"), data, data_len, cmd, seq);
 			return QQ_LOGIN_REPLY_ERR;
 	}
 	return QQ_LOGIN_REPLY_OK;
@@ -1096,7 +1141,7 @@
 			qq_process_buddy_check_code(gc, data, data_len);
 			break;
 		default:
-			process_unknow_cmd(gc, _("Unknow CLIENT CMD"), data, data_len, cmd, seq);
+			process_unknow_cmd(gc, _("Unknown CLIENT CMD"), data, data_len, cmd, seq);
 			is_unknow = TRUE;
 			break;
 	}
--- a/libpurple/protocols/qq/send_file.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/send_file.c	Thu Dec 11 06:24:14 2008 +0000
@@ -612,7 +612,8 @@
 	PurpleConnection *gc;
 	PurpleAccount *account;
 	guint32 to_uid;
-	gchar *filename, *filename_without_path;
+	const gchar *filename;
+	gchar *base_filename;
 
 	g_return_if_fail (xfer != NULL);
 	account = purple_xfer_get_account(xfer);
@@ -621,13 +622,14 @@
 	to_uid = purple_name_to_uid (xfer->who);
 	g_return_if_fail (to_uid != 0);
 
-	filename = (gchar *) purple_xfer_get_local_filename (xfer);
+	filename = purple_xfer_get_local_filename (xfer);
 	g_return_if_fail (filename != NULL);
 
-	filename_without_path = strrchr (filename, '/') + 1;
+	base_filename = g_path_get_basename(filename);
 
-	_qq_send_packet_file_request (gc, to_uid, filename_without_path,
+	_qq_send_packet_file_request (gc, to_uid, base_filename,
 			purple_xfer_get_size(xfer));
+	g_free(base_filename);
 }
 
 /* cancel the transfer of receiving files */
@@ -696,7 +698,7 @@
 		return;
 	}
 	*/
-	filename = strrchr(purple_xfer_get_local_filename(qd->xfer), '/') + 1;
+	filename = g_path_get_basename(purple_xfer_get_local_filename(qd->xfer));
 	msg = g_strdup_printf(_("%d has declined the file %s"),
 		 sender_uid, filename);
 
@@ -704,7 +706,8 @@
 	purple_xfer_request_denied(qd->xfer);
 	qd->xfer = NULL;
 
-	g_free (msg);
+	g_free(filename);
+	g_free(msg);
 }
 
 /* process cancel im for file transfer request */
@@ -725,7 +728,7 @@
 		return;
 	}
 	*/
-	filename = strrchr(purple_xfer_get_local_filename(qd->xfer), '/') + 1;
+	filename = g_path_get_basename(purple_xfer_get_local_filename(qd->xfer));
 	msg = g_strdup_printf
 		(_("%d canceled the transfer of %s"),
 		 sender_uid, filename);
@@ -734,7 +737,8 @@
 	purple_xfer_cancel_remote(qd->xfer);
 	qd->xfer = NULL;
 
-	g_free (msg);
+	g_free(filename);
+	g_free(msg);
 }
 
 /* process accept im for file transfer request */
--- a/libpurple/protocols/qq/utils.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/utils.c	Thu Dec 11 06:24:14 2008 +0000
@@ -39,8 +39,6 @@
 #include "util.h"
 #include "utils.h"
 
-#define QQ_NAME_FORMAT    "%d"
-
 /* These functions are used only in development phase */
 /*
    static void _qq_show_socket(gchar *desc, gint fd) {
@@ -135,7 +133,7 @@
 	guint32 ret;
 	g_return_val_if_fail(name != NULL, 0);
 
-	ret = strtol(name, NULL, 10);
+	ret = strtoul(name, NULL, 10);
 	if (errno == ERANGE)
 		return 0;
 	else
@@ -169,7 +167,7 @@
  * the return needs to be freed */
 gchar *uid_to_purple_name(guint32 uid)
 {
-	return g_strdup_printf(QQ_NAME_FORMAT, uid);
+	return g_strdup_printf("%u", uid);
 }
 
 /* try to dump the data as GBK */
@@ -339,3 +337,15 @@
 	qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ", buf, len, desc);
 }
 
+void qq_filter_str(gchar *str) {
+	gchar *temp;
+	if (str == NULL) {
+		return;
+	}
+
+	for (temp = str; *temp != 0; temp++) {
+		/*if (*temp == '\r' || *temp == '\n')  *temp = ' ';*/
+		if (*temp > 0 && *temp < 0x20)  *temp = ' ';
+	}
+	g_strstrip(str);
+}
--- a/libpurple/protocols/qq/utils.h	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/qq/utils.h	Thu Dec 11 06:24:14 2008 +0000
@@ -51,4 +51,5 @@
 		const char *format, ...);
 guint8 *hex_str_to_bytes(const gchar *buf, gint *out_len);
 
+void qq_filter_str(gchar *str);
 #endif
--- a/libpurple/protocols/yahoo/yahoo_aliases.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo_aliases.c	Thu Dec 11 06:24:14 2008 +0000
@@ -301,7 +301,7 @@
 						  "<ct a=\"1\" yi='%s' nn='%s' />\n</ab>\r\n",
 						  purple_account_get_username(gc->account),
 						  who, converted_alias_jp);
-			free(converted_alias_jp);
+			g_free(converted_alias_jp);
 			g_free(alias_jp);
 		} else {
 			gchar *escaped_alias = g_markup_escape_text(alias, -1);
@@ -321,7 +321,7 @@
 						  "<ct e=\"1\"  yi='%s' id='%s' nn='%s' pr='0' />\n</ab>\r\n",
 						  purple_account_get_username(gc->account),
 						  who, cb->id, converted_alias_jp);
-			free(converted_alias_jp);
+			g_free(converted_alias_jp);
 			g_free(alias_jp);
 		} else {
 			gchar *escaped_alias = g_markup_escape_text(alias, -1);
--- a/libpurple/protocols/yahoo/yahoo_profile.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo_profile.c	Thu Dec 11 06:24:14 2008 +0000
@@ -807,7 +807,7 @@
 	 */
 	if (error_message != NULL || url_text == NULL || strcmp(url_text, "") == 0) {
 		purple_notify_user_info_add_pair(user_info, _("Error retrieving profile"), NULL);
-		purple_notify_userinfo(info_data->gc, info_data->name, 
+		purple_notify_userinfo(info_data->gc, info_data->name,
 			user_info, NULL, NULL);
 		purple_notify_user_info_destroy(user_info);
 		g_free(profile_url_text);
@@ -841,10 +841,10 @@
 						 _("If you wish to view this profile, "
 						"you will need to visit this link in your web browser:"),
 						 profile_url_text, profile_url_text);
-		purple_notify_user_info_add_pair(user_info, NULL, tmp);		
+		purple_notify_user_info_add_pair(user_info, NULL, tmp);
 		g_free(tmp);
 
-		purple_notify_userinfo(info_data->gc, info_data->name, 
+		purple_notify_userinfo(info_data->gc, info_data->name,
 				user_info, NULL, NULL);
 
 		g_free(profile_url_text);
@@ -1193,17 +1193,15 @@
 
 	if(!found)
 	{
-		GString *str = g_string_new("");
+		const gchar *str;
 
-		g_string_append_printf(str, "<br><b>");
-		g_string_append_printf(str, _("User information for %s unavailable"),
-				info_data->name);
-		g_string_append_printf(str, "</b><br>");
+		purple_notify_user_info_add_section_break(user_info);
+		purple_notify_user_info_add_pair(user_info,
+				_("Error retrieving profile"), NULL);
 
 		if (profile_state == PROFILE_STATE_UNKNOWN_LANGUAGE) {
-			g_string_append_printf(str, "%s<br><br>",
-					_("Sorry, this profile seems to be in a language "
-					  "or format that is not supported at this time."));
+			str = _("This profile is in a language "
+					  "or format that is not supported at this time.");
 
 		} else if (profile_state == PROFILE_STATE_NOT_FOUND) {
 			PurpleBuddy *b = purple_find_buddy
@@ -1217,27 +1215,26 @@
 				 */
 				f = yahoo_friend_find(b->account->gc, b->name);
 			}
-			g_string_append_printf(str, "%s<br><br>",
-				f?  _("Could not retrieve the user's profile. "
+			str = f ? _("Could not retrieve the user's profile. "
 					  "This most likely is a temporary server-side problem. "
-					  "Please try again later."):
+					  "Please try again later.") :
 					_("Could not retrieve the user's profile. "
 					  "This most likely means that the user does not exist; "
 					  "however, Yahoo! sometimes does fail to find a user's "
 					  "profile. If you know that the user exists, "
-					  "please try again later."));
+					  "please try again later.");
 		} else {
-			g_string_append_printf(str, "%s<br><br>",
-					_("The user's profile is empty."));
+			str = _("The user's profile is empty.");
 		}
-		
-		purple_notify_user_info_add_pair(user_info, NULL, str->str);
-		g_string_free(str, TRUE);
+
+		purple_notify_user_info_add_pair(user_info, NULL, str);
 	}
 
 	/* put a link to the actual profile URL */
-	tmp = g_strdup_printf("<a href=\"%s\">%s</a>", profile_url_text, profile_url_text);
-	purple_notify_user_info_add_pair(user_info, _("Profile URL"), tmp);
+	purple_notify_user_info_add_section_break(user_info);
+	tmp = g_strdup_printf("<a href=\"%s\">%s</a>",
+			profile_url_text, _("View web profile"));
+	purple_notify_user_info_add_pair(user_info, NULL, tmp);
 	g_free(tmp);
 
 	g_free(stripped);
--- a/libpurple/protocols/zephyr/zephyr.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/protocols/zephyr/zephyr.c	Thu Dec 11 06:24:14 2008 +0000
@@ -1607,27 +1607,21 @@
 			gboolean found_ps = FALSE;
 			gchar ** tzc_cmd_array = g_strsplit(purple_account_get_string(gc->account,"tzc_command","/usr/bin/tzc -e %s")," ",0);
 			if (close(1) == -1) {
-				purple_debug_error("zephyr", "stdout couldn't be closed. dying\n");
 				exit(-1);
 			}
 			if (dup2(zephyr->fromtzc[1], 1) == -1) {
-				purple_debug_error("zephyr", "dup2 of stdout failed \n");
 				exit(-1);
 			}
 			if (close(zephyr->fromtzc[1]) == -1) {
-				purple_debug_error("zephyr", "closing of piped stdout failed\n");
 				exit(-1);
 			}
 			if (close(0) == -1) {
-				purple_debug_error("zephyr", "stdin couldn't be closed. dying\n");
 				exit(-1);
 			}
 			if (dup2(zephyr->totzc[0], 0) == -1) {
-				purple_debug_error("zephyr", "dup2 of stdin failed \n");
 				exit(-1);
 			}
 			if (close(zephyr->totzc[0]) == -1) {
-				purple_debug_error("zephyr", "closing of piped stdin failed\n");
 				exit(-1);
 			}
 			/* tzc_command should really be of the form 
@@ -1651,11 +1645,11 @@
 			}
 
 			if (!found_ps) {
-				purple_connection_error(gc,"Tzc command needs %s to set the exposure\n");
-				return;
+				exit(-1);
 			}
 
 			execvp(tzc_cmd_array[0], tzc_cmd_array);
+			exit(-1);
 		}
 		else {
 			fd_set rfds;
@@ -1667,6 +1661,7 @@
 			int parenlevel=0;
 			char* tempstr;
 			int tempstridx;
+			int select_status;
 
 			zephyr->tzc_pid = pid;
 			/* wait till we have data to read from ssh */
@@ -1678,11 +1673,19 @@
 
 			purple_debug_info("zephyr", "about to read from tzc\n");
 
-			select(zephyr->fromtzc[ZEPHYR_FD_READ] + 1, &rfds, NULL, NULL, NULL);
+			if (waitpid(pid, NULL, WNOHANG) == 0) { /* Only select if tzc is still running */
+				purple_debug_info("zephyr", "about to read from tzc\n");
+				select_status = select(zephyr->fromtzc[ZEPHYR_FD_READ] + 1, &rfds, NULL, NULL, NULL);
+			}
+			else {
+				purple_debug_info("zephyr", "tzc exited early\n");
+				select_status = -1;
+			}
 
 			FD_ZERO(&rfds);
 			FD_SET(zephyr->fromtzc[ZEPHYR_FD_READ], &rfds);
-			while (select(zephyr->fromtzc[ZEPHYR_FD_READ] + 1, &rfds, NULL, NULL, &tv)) {
+			while (select_status > 0 &&
+			       select(zephyr->fromtzc[ZEPHYR_FD_READ] + 1, &rfds, NULL, NULL, &tv) > 0) {
 				read(zephyr->fromtzc[ZEPHYR_FD_READ], bufcur, 1);
 				bufcur++;
 				if ((bufcur - buf) > (bufsize - 1)) {
--- a/libpurple/roomlist.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/roomlist.c	Thu Dec 11 06:24:14 2008 +0000
@@ -173,6 +173,7 @@
 	PurplePluginProtocolInfo *prpl_info = NULL;
 
 	g_return_val_if_fail(gc != NULL, NULL);
+	g_return_val_if_fail(PURPLE_CONNECTION_IS_CONNECTED(gc), NULL);
 
 	prpl = purple_connection_get_prpl(gc);
 
--- a/libpurple/win32/global.mak	Thu Dec 11 06:24:06 2008 +0000
+++ b/libpurple/win32/global.mak	Thu Dec 11 06:24:14 2008 +0000
@@ -16,11 +16,11 @@
 GTK_BIN ?= $(GTK_TOP)/bin
 BONJOUR_TOP ?= $(WIN32_DEV_TOP)/Bonjour_SDK
 LIBXML2_TOP ?= $(WIN32_DEV_TOP)/libxml2-2.6.30
-MEANWHILE_TOP ?= $(WIN32_DEV_TOP)/meanwhile-1.0.2_daa1
+MEANWHILE_TOP ?= $(WIN32_DEV_TOP)/meanwhile-1.0.2_daa2
 NSPR_TOP ?= $(WIN32_DEV_TOP)/nspr-4.6.4
 NSS_TOP ?= $(WIN32_DEV_TOP)/nss-3.11.4
 PERL_LIB_TOP ?= $(WIN32_DEV_TOP)/perl-5.10.0
-SILC_TOOLKIT ?= $(WIN32_DEV_TOP)/silc-toolkit-1.1.7
+SILC_TOOLKIT ?= $(WIN32_DEV_TOP)/silc-toolkit-1.1.8
 TCL_LIB_TOP ?= $(WIN32_DEV_TOP)/tcl-8.4.5
 GSTREAMER_TOP ?= $(WIN32_DEV_TOP)/gstreamer-0.10.13
 
--- a/pidgin/gtkaccount.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/pidgin/gtkaccount.c	Thu Dec 11 06:24:14 2008 +0000
@@ -2425,25 +2425,25 @@
 };
 
 static void
-authorize_and_add_cb(struct auth_and_add *aa)
+free_auth_and_add(struct auth_and_add *aa)
 {
-	aa->auth_cb(aa->data);
-	purple_blist_request_add_buddy(aa->account, aa->username,
-	 	                    NULL, aa->alias);
-
 	g_free(aa->username);
 	g_free(aa->alias);
 	g_free(aa);
 }
 
 static void
+authorize_and_add_cb(struct auth_and_add *aa)
+{
+	aa->auth_cb(aa->data);
+	purple_blist_request_add_buddy(aa->account, aa->username,
+	 	                    NULL, aa->alias);
+}
+
+static void
 deny_no_add_cb(struct auth_and_add *aa)
 {
 	aa->deny_cb(aa->data);
-
-	g_free(aa->username);
-	g_free(aa->alias);
-	g_free(aa);
 }
 
 static void *
@@ -2492,7 +2492,7 @@
 						  _("Authorize"), authorize_and_add_cb,
 						  _("Deny"), deny_no_add_cb,
 						  NULL);
-		g_object_set_data(G_OBJECT(alert), "auth_and_add", aa);
+		g_signal_connect_swapped(G_OBJECT(alert), "destroy", G_CALLBACK(free_auth_and_add), aa);
 	} else {
 		alert = pidgin_make_mini_dialog(gc, PIDGIN_STOCK_DIALOG_QUESTION,
 						  _("Authorize buddy?"), buffer, user_data,
@@ -2501,6 +2501,8 @@
 						  NULL);
 	}
 	pidgin_blist_add_alert(alert);
+	g_signal_connect(G_OBJECT(alert), "destroy",
+		G_CALLBACK(purple_account_request_close), NULL);
 
 	g_free(buffer);
 
@@ -2510,13 +2512,6 @@
 static void
 pidgin_accounts_request_close(void *ui_handle)
 {
-	/* This is super ugly, but without API changes, this is how it works */
-	struct auth_and_add *aa = g_object_get_data(G_OBJECT(ui_handle), "auth_and_add");
-	if (aa != NULL) {
-		g_free(aa->username);
-		g_free(aa->alias);
-		g_free(aa);
-	}
 	gtk_widget_destroy(GTK_WIDGET(ui_handle));
 }
 
--- a/pidgin/gtkblist.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/pidgin/gtkblist.c	Thu Dec 11 06:24:14 2008 +0000
@@ -3630,6 +3630,7 @@
 	const char *name = NULL;
 	char *filename, *path;
 	PurplePresence *p;
+	PurpleStatus *tune;
 
 	if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
 		if(!gtknode->contact_expanded) {
@@ -3668,7 +3669,21 @@
 		return _pidgin_blist_get_cached_emblem(path);
 	}
 
-	if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_TUNE)) {
+	tune = purple_presence_get_status(p, "tune");
+	if (tune && purple_status_is_active(tune)) {
+		/* Only in MSN.
+		 * TODO: Replace "Tune" with generalized "Media" in 3.0. */
+		if (purple_status_get_attr_string(tune, "game") != NULL) {
+			path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "game.png", NULL);
+			return _pidgin_blist_get_cached_emblem(path);
+		}
+		/* Only in MSN.
+		 * TODO: Replace "Tune" with generalized "Media" in 3.0. */
+		if (purple_status_get_attr_string(tune, "office") != NULL) {
+			path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "office.png", NULL);
+			return _pidgin_blist_get_cached_emblem(path);
+		}
+		/* Regular old "tune" is the only one in all protocols. */
 		path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "music.png", NULL);
 		return _pidgin_blist_get_cached_emblem(path);
 	}
--- a/pidgin/gtkroomlist.c	Thu Dec 11 06:24:06 2008 +0000
+++ b/pidgin/gtkroomlist.c	Thu Dec 11 06:24:14 2008 +0000
@@ -488,7 +488,7 @@
 	PurpleConnection *conn = purple_account_get_connection(account);
 	PurplePluginProtocolInfo *prpl_info = NULL;
 
-	if (conn)
+	if (conn && PURPLE_CONNECTION_IS_CONNECTED(conn))
 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(conn->prpl);
 
 	return (prpl_info && prpl_info->roomlist_get_list != NULL);