changeset 24952:bcad7dc4b453

propagate from branch 'im.pidgin.pidgin' (head 633ffe234af48ead3631ae62e23840b4438fcf5f) to branch 'im.pidgin.maiku.vv' (head 5585b8e919d9adb0eb491c714768027e81f1918c)
author Mike Ruprecht <maiku@soc.pidgin.im>
date Thu, 06 Nov 2008 03:20:05 +0000
parents 94224a5563db (current diff) 4acda11bf672 (diff)
children b9d4ab2c84c6
files configure.ac libpurple/plugins/ssl/Makefile.am libpurple/protocols/bonjour/bonjour.c libpurple/protocols/gg/gg.c libpurple/protocols/irc/irc.c libpurple/protocols/jabber/iq.c libpurple/protocols/qq/Makefile.am libpurple/protocols/qq/group_conv.c libpurple/protocols/qq/group_conv.h libpurple/protocols/qq/group_find.c libpurple/protocols/qq/group_find.h libpurple/protocols/qq/group_free.c libpurple/protocols/qq/group_free.h libpurple/protocols/qq/group_search.c libpurple/protocols/qq/group_search.h libpurple/protocols/qq/header_info.c libpurple/protocols/qq/header_info.h libpurple/protocols/qq/qq.c libpurple/protocols/qq/sys_msg.c libpurple/protocols/qq/sys_msg.h libpurple/prpl.c libpurple/server.c pidgin/Makefile.am pidgin/gtkconv.c pidgin/gtkconv.h pidgin/gtkdialogs.c pidgin/gtkprefs.c po/POTFILES.in
diffstat 135 files changed, 7963 insertions(+), 5756 deletions(-) [+]
line wrap: on
line diff
--- a/.mtn-ignore	Thu Nov 06 02:21:16 2008 +0000
+++ b/.mtn-ignore	Thu Nov 06 03:20:05 2008 +0000
@@ -13,6 +13,7 @@
 intltool-.*
 Doxyfile(\.mingw)?$
 aclocal.m4
+autogen.args
 compile
 config.cache
 config.guess
@@ -40,6 +41,7 @@
 pidgin/pixmaps/emotes/none/theme
 pidgin/plugins/musicmessaging/music-messaging-bindings.c
 pidgin/plugins/perl/common/Makefile.PL$
+pidgin/plugins/perl/common/Makefile.old
 pidgin/win32/pidgin_dll_rc.rc$
 pidgin/win32/pidgin_exe_rc.rc$
 install-sh
@@ -50,6 +52,7 @@
 libpurple/gconf/purple.schemas$
 libpurple/plugins/dbus-example-bindings.c
 libpurple/plugins/perl/common/Makefile.PL$
+libpurple/plugins/perl/common/Makefile.old
 libpurple/plugins/perl/common/const-c.inc
 libpurple/plugins/perl/common/const-xs.inc
 libpurple/plugins/perl/common/lib
--- a/COPYRIGHT	Thu Nov 06 02:21:16 2008 +0000
+++ b/COPYRIGHT	Thu Nov 06 03:20:05 2008 +0000
@@ -287,6 +287,7 @@
 Christopher O'Brien (siege)
 Peter O'Gorman
 Jon Oberheide
+Marcos García Ochoa
 Yusuke Odate
 Ruediger Oertel
 Gudmundur Bjarni Olafsson
@@ -305,6 +306,7 @@
 Ted Percival
 Eduardo Pérez
 Matt Perry
+Diego Pettenò
 Nathan Peterson
 Sebastián E. Peyrott
 Celso Pinto
--- a/ChangeLog	Thu Nov 06 02:21:16 2008 +0000
+++ b/ChangeLog	Thu Nov 06 03:20:05 2008 +0000
@@ -1,8 +1,29 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.5.3 (??/??/????):
+	libpurple:
+	* Fix an error with MSN offline messages by shipping the *new*
+	  "Microsoft Secure Server Authority" and the
+	  "Microsoft Internet Authority" certificates.  People that use
+	  --with-system-ssl-certs and GnuTLS need to include these in the
+	  system certs directory.
+
+	Pidgin:
+	* On GTK+ 2.14 and higher, we're using the gtk-tooltip-delay setting
+	  instead of our own (hidden) tooltip_delay pref.  If you had
+	  previously changed that pref, add a line like this to
+	  ~/.purple/gtkrc-2.0 (where 500 is the timeout (in ms) you want):
+	      gtk-tooltip-timeout = 500
+	  To completely disable tooltips (e.g. if you had an old tooltip_delay
+	  of zero), add this to ~/.purple/gtkrc-2.0:
+	      gtk-enable-tooltips = 0
+
+	Finch:
+	* Allow binding meta+arrow keys for actions.
+
+
 version 2.5.2 (10/19/2008):
 	libpurple:
-	* Restored the "Has You" feature to the MSN protocol tooltips.
 	* Fixed a crash on removing a custom buddy icon on a buddy.
 	* Fixed a crash caused by certain self-signed SSL certificates.
 	* Enable a number of strong ciphers which were previously disabled
@@ -16,14 +37,22 @@
 	* Added ability to change the color of visited links (using the theme
 	  control plugin, or setting the color in ~/.gtkrc-2.0)
 	* Fix a crash occuring when a custom smiley is deleted and re-added and
-	  used in an open conversation after being re-added
+	  used in an open conversation after being re-added.
 
 	Finch:
 	* A new 'Nested Grouping' option in the 'Grouping' plugin. Group
-	  hierarchies are defined by the '/' character in the group names
+	  hierarchies are defined by the '/' character in the group names.
 	* A bug was fixed where some key-bindings wouldn't work with some TERMs
 	  (e.g. xterm-color, screen-linux etc.)
 
+	MSN:
+	* Operations (such as moving to a new group) on contacts that were added
+	  in the same session should now complete correctly, and not cause
+	  synchronization errors at next login.
+	* Minor fixes to login process during a server transfer.
+	* Restored the "Has You" feature to the MSN protocol tooltips.
+	* ADL 205/214/etc errors should no longer prevent login.
+
 	XMPP:
 	* Sending and receiving custom smileys using the specification in
 	  XEP-0231 (bits of binary) and XHTML-IM
--- a/ChangeLog.win32	Thu Nov 06 02:21:16 2008 +0000
+++ b/ChangeLog.win32	Thu Nov 06 03:20:05 2008 +0000
@@ -1,5 +1,6 @@
 version 2.5.2 (10/19/2008):
-	* No changes
+	* Updated GTK+ to 2.12.12
+	  This will resolve an issue with stuff in QQ appearing as "(NULL)"
 
 version 2.5.1 (08/30/2008):
 	* No changes
--- a/Makefile.am	Thu Nov 06 02:21:16 2008 +0000
+++ b/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -30,7 +30,12 @@
 distcheck-hook: libpurple/plugins/perl/common/Purple.pm pidgin/plugins/perl/common/Pidgin.pm
 #	cp libpurple/plugins/perl/common/Gaim.pm $(distdir)/libpurple/plugins/perl/common
 
-version-check:
+commit-check:
+	(cd po ; intltool-update -m 2>&1 | grep -v '^mismatched quotes.*\.py$$' | sed "s|current directory|po directory|" | grep . ; if [ $$? = 0 ] ; then exit 1 ; else exit 0 ; fi)
+	LC_ALL=C sort -c -t/ -u po/POTFILES.in
+	LC_ALL=C sort -c -t/ -u po/POTFILES.skip
+
+version-check: commit-check
 # We don't want to release development versions.
 	test x`echo $(PACKAGE_VERSION) | grep dev` = x
 
--- a/NEWS	Thu Nov 06 02:21:16 2008 +0000
+++ b/NEWS	Thu Nov 06 03:20:05 2008 +0000
@@ -1,5 +1,7 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+Our development blog is available at: http://planet.pidgin.im
+
 2.5.2 (10/19/2008):
 	Ethan: After a bit of a struggle with our services, which put
 	this release off for an unfortunate length of time, we're
@@ -23,6 +25,12 @@
 	release far longer than we expected.  Hopefully the bugfixes make you
 	happy!
 
+	Elliott: This release took a while, but that was due to an unfortunate
+	server snafu. I didn't have much to do with it, but hopefully the new
+	servers will help us out a bit. Anyway, mostly bug-fixes this time.
+	Nothing spectacular, unless you happen to suffer from one of those bugs.
+	Oh, and don't forget, the "Has you" tooltip is back!
+
 2.5.1 (08/30/2008):
 	Kevin: This release is mainly a bug-fix release.  It solves a few
 	known crashes and updates some of our artwork.  Google's Summer of
--- a/autogen.sh	Thu Nov 06 02:21:16 2008 +0000
+++ b/autogen.sh	Thu Nov 06 03:20:05 2008 +0000
@@ -37,7 +37,7 @@
 #   INTLTOOLIZE_FLAGS - command line arguments to pass to intltoolize
 #   LIBTOOLIZE_FLAGS - command line arguments to pass to libtoolize
 #
-# Other helpfull notes:
+# Other helpful notes:
 #   If you're using a different c compiler, you can override the environment
 #   variable in 'autogen.args'.  For example, say you're using distcc, just add
 #   the following to 'autogen.args':
@@ -48,6 +48,8 @@
 ###############################################################################
 PACKAGE="Pidgin"
 ARGS_FILE="autogen.args"
+export CFLAGS
+export LDFLAGS
 
 libtoolize="libtoolize"
 case $(uname -s) in
@@ -115,7 +117,7 @@
 if [ -f ${ARGS_FILE} ] ; then
 	echo "found."
 	printf "%s" "sourcing ${ARGS_FILE}: "
-	. ${ARGS_FILE}
+	. "`dirname "$0"`"/${ARGS_FILE}
 	echo "done."
 else
 	echo "not found."
@@ -125,8 +127,8 @@
 # Check for our required helpers
 ###############################################################################
 check "$libtoolize";		LIBTOOLIZE=${BIN};
-check "glib-gettextize"; GLIB_GETTEXTIZE=${BIN};
-check "intltoolize";	INTLTOOLIZE=${BIN};
+check "glib-gettextize";	GLIB_GETTEXTIZE=${BIN};
+check "intltoolize";		INTLTOOLIZE=${BIN};
 check "aclocal";		ACLOCAL=${BIN};
 check "autoheader";		AUTOHEADER=${BIN};
 check "automake";		AUTOMAKE=${BIN};
--- a/configure.ac	Thu Nov 06 02:21:16 2008 +0000
+++ b/configure.ac	Thu Nov 06 03:20:05 2008 +0000
@@ -46,7 +46,7 @@
 m4_define([purple_lt_current], [5])
 m4_define([purple_major_version], [2])
 m4_define([purple_minor_version], [5])
-m4_define([purple_micro_version], [2])
+m4_define([purple_micro_version], [3])
 m4_define([purple_version_suffix], [vv-devel])
 m4_define([purple_version],
           [purple_major_version.purple_minor_version.purple_micro_version])
@@ -55,7 +55,7 @@
 m4_define([gnt_lt_current], [5])
 m4_define([gnt_major_version], [2])
 m4_define([gnt_minor_version], [5])
-m4_define([gnt_micro_version], [2])
+m4_define([gnt_micro_version], [3])
 m4_define([gnt_version_suffix], [vv-devel])
 m4_define([gnt_version],
           [gnt_major_version.gnt_minor_version.gnt_micro_version])
@@ -74,7 +74,7 @@
 
 AC_CANONICAL_SYSTEM
 AM_CONFIG_HEADER(config.h)
-AM_INIT_AUTOMAKE([dist-bzip2])
+AM_INIT_AUTOMAKE([1.9 -Wno-portability dist-bzip2])
 
 PURPLE_MAJOR_VERSION=purple_major_version
 PURPLE_MINOR_VERSION=purple_minor_version
@@ -1196,6 +1196,7 @@
 
 AC_ARG_ENABLE(fortify, [AC_HELP_STRING([--disable-fortify], [compile without FORTIFY_SOURCE support])], , enable_fortify=yes)
 
+DEBUG_CFLAGS="$DEBUG_CFLAGS -DPURPLE_DISABLE_DEPRECATED -DPIDGIN_DISABLE_DEPRECATED -DFINCH_DISABLE_DEPRECATED -DGNT_DISABLE_DEPRECATED"
 if test "x$GCC" = "xyes"; then
 	dnl We enable -Wall later.
 	dnl If it's set after the warning CFLAGS in the compiler invocation, it counteracts the -Wno... flags.
--- a/finch/gntblist.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/finch/gntblist.c	Thu Nov 06 03:20:05 2008 +0000
@@ -1288,6 +1288,17 @@
 }
 
 static void
+toggle_show_offline(GntMenuItem *item, gpointer buddy)
+{
+	purple_blist_node_set_bool(buddy, "show_offline",
+			!purple_blist_node_get_bool(buddy, "show_offline"));
+	if (!ggblist->manager->can_add_node(buddy))
+		node_remove(purple_get_blist(), buddy);
+	else
+		node_update(purple_get_blist(), buddy);
+}
+
+static void
 create_buddy_menu(GntMenu *menu, PurpleBuddy *buddy)
 {
 	PurpleAccount *account;
@@ -1322,10 +1333,10 @@
 	gnt_menuitem_set_callback(item, toggle_block_buddy, buddy);
 	gnt_menu_add_item(menu, item);
 
-#if 0
-	add_custom_action(tree, _("View Log"),
-			PURPLE_CALLBACK(finch_blist_view_log_cb)), buddy);
-#endif
+	item = gnt_menuitem_check_new(_("Show when offline"));
+	gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item), purple_blist_node_get_bool((PurpleBlistNode*)buddy, "show_offline"));
+	gnt_menuitem_set_callback(item, toggle_show_offline, buddy);
+	gnt_menu_add_item(menu, item);
 
 	/* Protocol actions */
 	append_proto_menu(menu,
--- a/finch/libgnt/gntkeys.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/finch/libgnt/gntkeys.c	Thu Nov 06 03:20:05 2008 +0000
@@ -102,7 +102,12 @@
 #define INSERT_COMB(k, code) do { \
 		snprintf(key, sizeof(key), "%s%s%s", controls[c], alts[a], k);  \
 		INSERT_KEY(key, code);  \
-	} while (0);
+	} while (0)
+#define INSERT_COMB_CODE(k, c1, c2) do { \
+		char __[32]; \
+		snprintf(__, sizeof(__), "%s%s", c1, c2); \
+		INSERT_COMB(k, __); \
+	} while (0)
 
 	/* Lower-case alphabets */
 	for (a = 0, c = 0; controls[c]; c++, a = 0) {
@@ -124,6 +129,10 @@
 			}
 			if (c == 0) {
 				INSERT_COMB("tab", "\033\t");
+				INSERT_COMB_CODE("up", "\033", GNT_KEY_UP);
+				INSERT_COMB_CODE("down", "\033", GNT_KEY_DOWN);
+				INSERT_COMB_CODE("left", "\033", GNT_KEY_LEFT);
+				INSERT_COMB_CODE("right", "\033", GNT_KEY_RIGHT);
 			}
 		}
 	}
@@ -144,6 +153,8 @@
 
 void gnt_keys_refine(char *text)
 {
+	while (*text == 27 && *(text + 1) == 27)
+		text++;
 	if (*text == 27 && *(text + 1) == '[' &&
 			(*(text + 2) >= 'A' && *(text + 2) <= 'D')) {
 		/* Apparently this is necessary for urxvt and screen and xterm */
--- a/libpurple/account.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/account.c	Thu Nov 06 03:20:05 2008 +0000
@@ -1035,7 +1035,7 @@
 	purple_debug_info("account", "Registering account %s\n",
 					purple_account_get_username(account));
 
-	purple_connection_new(account, TRUE, purple_account_get_password(account));
+	_purple_connection_new(account, TRUE, purple_account_get_password(account));
 }
 
 void
@@ -1046,7 +1046,7 @@
 	purple_debug_info("account", "Unregistering account %s\n",
 					  purple_account_get_username(account));
 
-	purple_connection_new_unregister(account, purple_account_get_password(account), cb, user_data);
+	_purple_connection_new_unregister(account, purple_account_get_password(account), cb, user_data);
 }
 
 static void
@@ -1069,7 +1069,7 @@
 
 	purple_account_set_password(account, entry);
 
-	purple_connection_new(account, FALSE, entry);
+	_purple_connection_new(account, FALSE, entry);
 }
 
 static void
@@ -1155,7 +1155,7 @@
 		!(prpl_info->options & OPT_PROTO_PASSWORD_OPTIONAL))
 		purple_account_request_password(account, G_CALLBACK(request_password_ok_cb), G_CALLBACK(request_password_cancel_cb), account);
 	else
-		purple_connection_new(account, FALSE, password);
+		_purple_connection_new(account, FALSE, password);
 }
 
 void
@@ -1171,7 +1171,7 @@
 	account->disconnecting = TRUE;
 
 	gc = purple_account_get_connection(account);
-	purple_connection_destroy(gc);
+	_purple_connection_destroy(gc);
 	if (!purple_account_get_remember_password(account))
 		purple_account_set_password(account, NULL);
 	purple_account_set_connection(account, NULL);
--- a/libpurple/blist.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/blist.c	Thu Nov 06 03:20:05 2008 +0000
@@ -20,6 +20,8 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
+#define _PURPLE_BLIST_C_
+
 #include "internal.h"
 #include "blist.h"
 #include "conversation.h"
@@ -41,7 +43,6 @@
 static guint          save_timer = 0;
 static gboolean       blist_loaded = FALSE;
 
-
 /*********************************************************************
  * Private utility functions                                         *
  *********************************************************************/
@@ -446,7 +447,7 @@
 			purple_blist_get_last_child((PurpleBlistNode*)group));
 
 	if ((alias = xmlnode_get_attrib(cnode, "alias"))) {
-		purple_contact_set_alias(contact, alias);
+		purple_blist_alias_contact(contact, alias);
 	}
 
 	for (x = cnode->child; x; x = x->next) {
@@ -835,13 +836,11 @@
 		ops->update(purplebuddylist, node);
 }
 
-#ifndef PURPLE_DISABLE_DEPRECATED
 void
 purple_blist_update_buddy_icon(PurpleBuddy *buddy)
 {
 	purple_blist_update_node_icon((PurpleBlistNode *)buddy);
 }
-#endif
 
 /*
  * TODO: Maybe remove the call to this from server.c and call it
--- a/libpurple/blist.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/blist.h	Thu Nov 06 03:20:05 2008 +0000
@@ -75,11 +75,11 @@
 
 } PurpleBlistNodeFlags;
 
-#define PURPLE_BLIST_NODE_HAS_FLAG(b, f) (((PurpleBlistNode*)(b))->flags & (f))
+#define PURPLE_BLIST_NODE_HAS_FLAG(b, f) (purple_blist_node_get_flags((PurpleBlistNode*)(b)) & (f))
 #define PURPLE_BLIST_NODE_SHOULD_SAVE(b) (! PURPLE_BLIST_NODE_HAS_FLAG(b, PURPLE_BLIST_NODE_FLAG_NO_SAVE))
 
-#define PURPLE_BLIST_NODE_NAME(n) ((n)->type == PURPLE_BLIST_CHAT_NODE  ? purple_chat_get_name((PurpleChat*)n) :        \
-				     (n)->type == PURPLE_BLIST_BUDDY_NODE ? purple_buddy_get_name((PurpleBuddy*)n) : NULL)
+#define PURPLE_BLIST_NODE_NAME(n) (purple_blist_node_get_type(n) == PURPLE_BLIST_CHAT_NODE  ? purple_chat_get_name((PurpleChat*)n) :        \
+				     purple_blist_node_get_type(n) == PURPLE_BLIST_BUDDY_NODE ? purple_buddy_get_name((PurpleBuddy*)n) : NULL)
 
 #include "account.h"
 #include "buddyicon.h"
@@ -89,6 +89,8 @@
 /* Data Structures                                                        */
 /**************************************************************************/
 
+#if !(defined PURPLE_HIDE_STRUCTS) || (defined _PURPLE_BLIST_C_)
+
 /**
  * A Buddy list node.  This can represent a group, a buddy, or anything else.
  * This is a base class for struct buddy and struct group and for anything
@@ -154,6 +156,8 @@
 	PurpleAccount *account; /**< The account this chat is attached to */
 };
 
+#endif /* PURPLE_HIDE_STRUCTS && PURPLE_BLIST_STRUCTS */
+
 
 /**
  * The Buddy List
@@ -331,7 +335,7 @@
  */
 void purple_blist_update_node_icon(PurpleBlistNode *node);
 
-#ifndef PURPLE_DISABLE_DEPRECATED
+#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_BLIST_C_)
 /**
  * Updates a buddy's icon.
  *
@@ -557,7 +561,7 @@
  */
 PurpleBuddy *purple_contact_get_priority_buddy(PurpleContact *contact);
 
-#ifndef PURPLE_DISABLE_DEPRECATED
+#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_BLIST_C_)
 /**
  * Sets the alias for a contact.
  *
--- a/libpurple/buddyicon.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/buddyicon.c	Thu Nov 06 03:20:05 2008 +0000
@@ -23,6 +23,8 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+#define _PURPLE_BUDDYICON_C_
+
 #include "internal.h"
 #include "buddyicon.h"
 #include "conversation.h"
@@ -954,7 +956,6 @@
 	return purple_buddy_icons_node_set_custom_icon(node, data, len);
 }
 
-#ifndef PURPLE_DISABLE_DEPRECATED
 gboolean
 purple_buddy_icons_has_custom_icon(PurpleContact *contact)
 {
@@ -973,7 +974,6 @@
 {
 	return purple_buddy_icons_node_set_custom_icon((PurpleBlistNode*)contact, icon_data, icon_len);
 }
-#endif
 
 void
 _purple_buddy_icon_set_old_icons_dir(const char *dirname)
--- a/libpurple/buddyicon.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/buddyicon.h	Thu Nov 06 03:20:05 2008 +0000
@@ -337,7 +337,7 @@
 purple_buddy_icons_node_set_custom_icon_from_file(PurpleBlistNode *node,
                                                   const gchar *filename);
 
-#ifndef PURPLE_DISABLE_DEPRECATED
+#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_BUDDYICON_C_)
 /**
  * PurpleContact version of purple_buddy_icons_node_has_custom_icon.
  *
--- a/libpurple/certificate.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/certificate.c	Thu Nov 06 03:20:05 2008 +0000
@@ -137,17 +137,17 @@
 GList *
 purple_certificate_copy_list(GList *crt_list)
 {
-	GList *new, *l;
+	GList *new_l, *l;
 
 	/* First, make a shallow copy of the list */
-	new = g_list_copy(crt_list);
+	new_l = g_list_copy(crt_list);
 
 	/* Now go through and actually duplicate each certificate */
-	for (l = new; l; l = l->next) {
+	for (l = new_l; l; l = l->next) {
 		l->data = purple_certificate_copy(l->data);
 	}
 
-	return new;
+	return new_l;
 }
 
 void
@@ -1897,10 +1897,13 @@
 
 	/* Make messages */
 	secondary = g_strdup_printf(_("Common name: %s\n\n"
-				      "Fingerprint (SHA1): %s\n\n"
-				      "Activation date: %s\n"
-				      "Expiration date: %s\n"),
-				    cn, sha_asc, activ_str, expir_str);
+								  "Fingerprint (SHA1): %s\n\n"
+								  "Activation date: %s\n"
+								  "Expiration date: %s\n"),
+								cn ? cn : "(null)",
+								sha_asc ? sha_asc : "(null)",
+								activ_str ? activ_str : "(null)",
+								expir_str ? expir_str : "(null)");
 
 	/* Make a semi-pretty display */
 	purple_notify_info(
--- a/libpurple/circbuffer.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/circbuffer.c	Thu Nov 06 03:20:05 2008 +0000
@@ -109,13 +109,12 @@
 	else
 		len_stored = len;
 
-	memcpy(buf->inptr, src, len_stored);
+	if (len_stored > 0)
+		memcpy(buf->inptr, src, len_stored);
 
 	if (len_stored < len) {
 		memcpy(buf->buffer, (char*)src + len_stored, len - len_stored);
 		buf->inptr = buf->buffer + (len - len_stored);
-	} else if ((buf->buffer - buf->inptr) == len_stored) {
-		buf->inptr = buf->buffer;
 	} else {
 		buf->inptr += len_stored;
 	}
--- a/libpurple/connection.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/connection.c	Thu Nov 06 03:20:05 2008 +0000
@@ -23,6 +23,8 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+#define _PURPLE_CONNECTION_C_
+
 #include "internal.h"
 #include "account.h"
 #include "blist.h"
@@ -99,6 +101,12 @@
 void
 purple_connection_new(PurpleAccount *account, gboolean regist, const char *password)
 {
+	_purple_connection_new(account, regist, password);
+}
+
+void
+_purple_connection_new(PurpleAccount *account, gboolean regist, const char *password)
+{
 	PurpleConnection *gc;
 	PurplePlugin *prpl;
 	PurplePluginProtocolInfo *prpl_info;
@@ -170,9 +178,14 @@
 		prpl_info->login(account);
 	}
 }
+void
+purple_connection_new_unregister(PurpleAccount *account, const char *password, PurpleAccountUnregistrationCb cb, void *user_data)
+{
+	_purple_connection_new_unregister(account, password, cb, user_data);
+}
 
 void
-purple_connection_new_unregister(PurpleAccount *account, const char *password, PurpleAccountUnregistrationCb cb, void *user_data)
+_purple_connection_new_unregister(PurpleAccount *account, const char *password, PurpleAccountUnregistrationCb cb, void *user_data)
 {
 	/* Lots of copy/pasted code to avoid API changes. You might want to integrate that into the previous function when posssible. */
 	PurpleConnection *gc;
@@ -230,6 +243,12 @@
 void
 purple_connection_destroy(PurpleConnection *gc)
 {
+	_purple_connection_destroy(gc);
+}
+
+void
+_purple_connection_destroy(PurpleConnection *gc)
+{
 	PurpleAccount *account;
 	GSList *buddies;
 	PurplePluginProtocolInfo *prpl_info = NULL;
--- a/libpurple/connection.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/connection.h	Thu Nov 06 03:20:05 2008 +0000
@@ -268,7 +268,7 @@
 /**************************************************************************/
 /*@{*/
 
-#ifndef PURPLE_DISABLE_DEPRECATED
+#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_CONNECTION_C_)
 /**
  * This function should only be called by purple_account_connect()
  * in account.c.  If you're trying to sign on an account, use that
@@ -292,7 +292,7 @@
 									const char *password);
 #endif
 
-#ifndef PURPLE_DISABLE_DEPRECATED
+#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_CONNECTION_C_)
 /**
  * This function should only be called by purple_account_unregister()
  * in account.c.
@@ -310,7 +310,7 @@
 void purple_connection_new_unregister(PurpleAccount *account, const char *password, PurpleAccountUnregistrationCb cb, void *user_data);
 #endif
 
-#ifndef PURPLE_DISABLE_DEPRECATED
+#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_CONNECTION_C_)
 /**
  * Disconnects and destroys a PurpleConnection.
  *
--- a/libpurple/core.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/core.c	Thu Nov 06 03:20:05 2008 +0000
@@ -361,7 +361,7 @@
 #endif
 	if (g_rename(path, new_name))
 	{
-		purple_debug_error("core", "Error renaming %s to %s: %s. Please report this at http://developer.pidgin.im\n",
+		purple_debug_error("core", "Error renaming %s to %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
 		                   path, new_name, g_strerror(errno));
 		g_free(new_name);
 		return FALSE;
@@ -374,7 +374,7 @@
 	old_name = g_build_filename(old_base, basename, NULL);
 	if (symlink(new_name, old_name))
 	{
-		purple_debug_warning("core", "Error symlinking %s to %s: %s. Please report this at http://developer.pidgin.im\n",
+		purple_debug_warning("core", "Error symlinking %s to %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
 		                     old_name, new_name, g_strerror(errno));
 	}
 	g_free(old_name);
@@ -430,7 +430,7 @@
 	{
 		if (g_mkdir(user_dir, S_IRUSR | S_IWUSR | S_IXUSR) == -1)
 		{
-			purple_debug_error("core", "Error creating directory %s: %s. Please report this at http://developer.pidgin.im\n",
+			purple_debug_error("core", "Error creating directory %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
 			                   user_dir, g_strerror(errno));
 			g_free(status_file);
 			g_free(old_user_dir);
@@ -442,7 +442,7 @@
 	 * incomplete migrations and properly retry. */
 	if (!(fp = g_fopen(status_file, "w")))
 	{
-		purple_debug_error("core", "Error opening file %s for writing: %s. Please report this at http://developer.pidgin.im\n",
+		purple_debug_error("core", "Error opening file %s for writing: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
 		                   status_file, g_strerror(errno));
 		g_free(status_file);
 		g_free(old_user_dir);
@@ -454,7 +454,7 @@
 	err = NULL;
 	if (!(dir = g_dir_open(old_user_dir, 0, &err)))
 	{
-		purple_debug_error("core", "Error opening directory %s: %s. Please report this at http://developer.pidgin.im\n",
+		purple_debug_error("core", "Error opening directory %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
 		                   status_file,
 		                   (err ? err->message : "Unknown error"));
 		if (err)
@@ -483,7 +483,7 @@
 				if ((link = g_file_read_link(name, &err)) == NULL)
 				{
 					char *name_utf8 = g_filename_to_utf8(name, -1, NULL, NULL, NULL);
-					purple_debug_error("core", "Error reading symlink %s: %s. Please report this at http://developer.pidgin.im\n",
+					purple_debug_error("core", "Error reading symlink %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
 					                   name_utf8 ? name_utf8 : name, err->message);
 					g_free(name_utf8);
 					g_error_free(err);
@@ -500,7 +500,7 @@
 				if ((linklen = readlink(name, buf, sizeof(buf) - 1) == -1))
 				{
 					char *name_utf8 = g_filename_to_utf8(name, -1, NULL, NULL, NULL);
-					purple_debug_error("core", "Error reading symlink %s: %s. Please report this at http://developer.pidgin.im\n",
+					purple_debug_error("core", "Error reading symlink %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
 					                   name_utf8, g_strerror(errno));
 					g_free(name_utf8);
 					g_free(name);
@@ -538,7 +538,7 @@
 				 * guaranteed.  Oh well. */
 				if (symlink(link, logs_dir))
 				{
-					purple_debug_error("core", "Error symlinking %s to %s: %s. Please report this at http://developer.pidgin.im\n",
+					purple_debug_error("core", "Error symlinking %s to %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
 					                   logs_dir, link, g_strerror(errno));
 					g_free(link);
 					g_free(name);
@@ -577,7 +577,7 @@
 				err = NULL;
 				if (!(icons_dir = g_dir_open(name, 0, &err)))
 				{
-					purple_debug_error("core", "Error opening directory %s: %s. Please report this at http://developer.pidgin.im\n",
+					purple_debug_error("core", "Error opening directory %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
 					                   name,
 					                   (err ? err->message : "Unknown error"));
 					if (err)
@@ -595,7 +595,7 @@
 				{
 					if (g_mkdir(new_icons_dir, S_IRUSR | S_IWUSR | S_IXUSR) == -1)
 					{
-						purple_debug_error("core", "Error creating directory %s: %s. Please report this at http://developer.pidgin.im\n",
+						purple_debug_error("core", "Error creating directory %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
 						                   new_icons_dir, g_strerror(errno));
 						g_free(new_icons_dir);
 						g_dir_close(icons_dir);
@@ -658,7 +658,7 @@
 
 			if (!(fp = g_fopen(name, "rb")))
 			{
-				purple_debug_error("core", "Error opening file %s for reading: %s. Please report this at http://developer.pidgin.im\n",
+				purple_debug_error("core", "Error opening file %s for reading: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
 				                   name, g_strerror(errno));
 				g_free(name);
 				g_dir_close(dir);
@@ -670,7 +670,7 @@
 			new_name = g_build_filename(user_dir, entry, NULL);
 			if (!(new_file = g_fopen(new_name, "wb")))
 			{
-				purple_debug_error("core", "Error opening file %s for writing: %s. Please report this at http://developer.pidgin.im\n",
+				purple_debug_error("core", "Error opening file %s for writing: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
 				                   new_name, g_strerror(errno));
 				fclose(fp);
 				g_free(new_name);
@@ -689,7 +689,7 @@
 				size = fread(buf, 1, sizeof(buf), fp);
 				if (size != sizeof(buf) && !feof(fp))
 				{
-					purple_debug_error("core", "Error reading %s: %s. Please report this at http://developer.pidgin.im\n",
+					purple_debug_error("core", "Error reading %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
 					                   name, g_strerror(errno));
 					fclose(new_file);
 					fclose(fp);
@@ -703,7 +703,7 @@
 
 				if (!fwrite(buf, size, 1, new_file) && ferror(new_file) != 0)
 				{
-					purple_debug_error("core", "Error writing %s: %s. Please report this at http://developer.pidgin.im\n",
+					purple_debug_error("core", "Error writing %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
 					                   new_name, g_strerror(errno));
 					fclose(new_file);
 					fclose(fp);
@@ -718,7 +718,7 @@
 
 			if (fclose(new_file))
 			{
-				purple_debug_error("core", "Error writing: %s: %s. Please report this at http://developer.pidgin.im\n",
+				purple_debug_error("core", "Error writing: %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
 				                   new_name, g_strerror(errno));
 			}
 			if (fclose(fp))
@@ -737,7 +737,7 @@
 	/* The migration was successful, so delete the status file. */
 	if (g_unlink(status_file))
 	{
-		purple_debug_error("core", "Error unlinking file %s: %s. Please report this at http://developer.pidgin.im\n",
+		purple_debug_error("core", "Error unlinking file %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE "\n",
 		                   status_file, g_strerror(errno));
 		g_free(status_file);
 		return FALSE;
--- a/libpurple/dbus-server.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/dbus-server.c	Thu Nov 06 03:20:05 2008 +0000
@@ -29,6 +29,10 @@
 #include <stdlib.h>
 #include <string.h>
 
+/* Allow the code below to see deprecated functions, so we can continue to
+ * export them via DBus. */
+#undef PURPLE_DISABLE_DEPRECATED
+
 #include "account.h"
 #include "blist.h"
 #include "conversation.h"
--- a/libpurple/internal.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/internal.h	Thu Nov 06 03:20:05 2008 +0000
@@ -231,6 +231,12 @@
 #define PURPLE_WEBSITE "http://pidgin.im/"
 #define PURPLE_DEVEL_WEBSITE "http://developer.pidgin.im/"
 
+
+/* INTERNAL FUNCTIONS */
+
+#include "account.h"
+#include "connection.h"
+
 /* This is for the accounts code to notify the buddy icon code that
  * it's done loading.  We may want to replace this with a signal. */
 void
@@ -247,4 +253,48 @@
 void
 _purple_buddy_icon_set_old_icons_dir(const char *dirname);
 
+/**
+ * Creates a connection to the specified account and either connects
+ * or attempts to register a new account.  If you are logging in,
+ * the connection uses the current active status for this account.
+ * So if you want to sign on as "away," for example, you need to
+ * have called purple_account_set_status(account, "away").
+ * (And this will call purple_account_connect() automatically).
+ *
+ * @note This function should only be called by purple_account_connect()
+ *       in account.c.  If you're trying to sign on an account, use that
+ *       function instead.
+ *
+ * @param account  The account the connection should be connecting to.
+ * @param regist   Whether we are registering a new account or just
+ *                 trying to do a normal signon.
+ * @param password The password to use.
+ */
+void _purple_connection_new(PurpleAccount *account, gboolean regist,
+                            const char *password);
+/**
+ * Tries to unregister the account on the server. If the account is not
+ * connected, also creates a new connection.
+ *
+ * @note This function should only be called by purple_account_unregister()
+ *       in account.c.
+ *
+ * @param account  The account to unregister
+ * @param password The password to use.
+ * @param cb Optional callback to be called when unregistration is complete
+ * @param user_data user data to pass to the callback
+ */
+void _purple_connection_new_unregister(PurpleAccount *account, const char *password,
+                                       PurpleAccountUnregistrationCb cb, void *user_data);
+/**
+ * Disconnects and destroys a PurpleConnection.
+ *
+ * @note This function should only be called by purple_account_disconnect()
+ *        in account.c.  If you're trying to sign off an account, use that
+ *        function instead.
+ *
+ * @param gc The purple connection to destroy.
+ */
+void _purple_connection_destroy(PurpleConnection *gc);
+
 #endif /* _PURPLE_INTERNAL_H_ */
--- a/libpurple/network.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/network.c	Thu Nov 06 03:20:05 2008 +0000
@@ -66,6 +66,8 @@
 static DBusGConnection *nm_conn = NULL;
 static DBusGProxy *nm_proxy = NULL;
 static DBusGProxy *dbus_proxy = NULL;
+static NMState nm_state = NM_STATE_UNKNOWN;
+static gboolean have_nm_state = FALSE;
 
 #elif defined _WIN32
 static int current_network_count;
@@ -596,13 +598,15 @@
 purple_network_is_available(void)
 {
 #ifdef HAVE_NETWORKMANAGER
-	NMState state = nm_get_network_state();
-	if (state == NM_STATE_UNKNOWN)
+	if (!have_nm_state)
 	{
-		purple_debug_warning("network", "NetworkManager not active. Assuming connection exists.\n");
-		return TRUE;
+		have_nm_state = TRUE;
+		nm_state = nm_get_network_state();
+		if (nm_state == NM_STATE_UNKNOWN)
+			purple_debug_warning("network", "NetworkManager not active. Assuming connection exists.\n");
 	}
-	else if (state == NM_STATE_CONNECTED)
+
+	if (nm_state == NM_STATE_UNKNOWN || nm_state == NM_STATE_CONNECTED)
 		return TRUE;
 
 	return FALSE;
@@ -618,9 +622,12 @@
 static void
 nm_update_state(NMState state)
 {
-	static NMState prev = NM_STATE_UNKNOWN;
+	NMState prev = nm_state;
 	PurpleConnectionUiOps *ui_ops = purple_connections_get_ui_ops();
 
+	have_nm_state = TRUE;
+	nm_state = state;
+
 	purple_signal_emit(purple_network_get_handle(), "network-configuration-changed", NULL);
 
 	switch(state)
@@ -630,16 +637,14 @@
 			res_init();
 			if (ui_ops != NULL && ui_ops->network_connected != NULL)
 				ui_ops->network_connected();
-			prev = state;
 			break;
 		case NM_STATE_ASLEEP:
 		case NM_STATE_CONNECTING:
 		case NM_STATE_DISCONNECTED:
-			if (prev != NM_STATE_CONNECTED)
+			if (prev != NM_STATE_CONNECTED && prev != NM_STATE_UNKNOWN)
 				break;
 			if (ui_ops != NULL && ui_ops->network_disconnected != NULL)
 				ui_ops->network_disconnected();
-			prev = state;
 			break;
 		case NM_STATE_UNKNOWN:
 		default:
@@ -664,7 +669,6 @@
 		return NM_STATE_UNKNOWN;
 
 	if (!dbus_g_proxy_call(nm_proxy, "state", &err, G_TYPE_INVALID, G_TYPE_UINT, &state, G_TYPE_INVALID)) {
-		/* XXX: Print an error? */
 		g_error_free(err);
 		return NM_STATE_UNKNOWN;
 	}
--- a/libpurple/network.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/network.h	Thu Nov 06 03:20:05 2008 +0000
@@ -106,7 +106,6 @@
  */
 const char *purple_network_get_my_ip(int fd);
 
-#ifndef PURPLE_DISABLE_DEPRECATED
 /**
  * Should calls to purple_network_listen() and purple_network_listen_range()
  * map the port externally using NAT-PMP or UPnP?
@@ -118,7 +117,6 @@
  * @since 2.3.0
  */
 void purple_network_listen_map_external(gboolean map_external);
-#endif
 
 /**
  * Attempts to open a listening port ONLY on the specified port number.
@@ -203,11 +201,9 @@
 unsigned short purple_network_get_port_from_fd(int fd);
 
 /**
- * Detects if there is an available Internet connection. Note that this call
- * could block for the amount of time specified in inet_detect_timeout, so
- * using it in a UI thread may cause uncomfortableness
+ * Detects if there is an available network connection.
  *
- * @return TRUE if the Internet is available
+ * @return TRUE if the network is available
  */
 gboolean purple_network_is_available(void);
 
--- a/libpurple/notify.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/notify.c	Thu Nov 06 03:20:05 2008 +0000
@@ -23,6 +23,8 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+#define _PURPLE_NOTIFY_C_
+
 #include "internal.h"
 #include "dbus-maybe.h"
 #include "notify.h"
--- a/libpurple/notify.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/notify.h	Thu Nov 06 03:20:05 2008 +0000
@@ -289,7 +289,7 @@
  */
 void purple_notify_searchresults_row_add(PurpleNotifySearchResults *results,
 									   GList *row);
-#ifndef PURPLE_DISABLE_DEPRECATED
+#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_NOTIFY_C_)
 /**
  * Returns a number of the rows in the search results object.
  *
@@ -310,7 +310,7 @@
 guint purple_notify_searchresults_get_rows_count(PurpleNotifySearchResults *results);
 #endif
 
-#ifndef PURPLE_DISABLE_DEPRECATED
+#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_NOTIFY_C_)
 /**
  * Returns a number of the columns in the search results object.
  *
@@ -331,7 +331,7 @@
 guint purple_notify_searchresults_get_columns_count(PurpleNotifySearchResults *results);
 #endif
 
-#ifndef PURPLE_DISABLE_DEPRECATED
+#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_NOTIFY_C_)
 /**
  * Returns a row of the results from the search results object.
  *
@@ -354,7 +354,7 @@
 										 unsigned int row_id);
 #endif
 
-#ifndef PURPLE_DISABLE_DEPRECATED
+#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_NOTIFY_C_)
 /**
  * Returns a title of the search results object's column.
  *
--- a/libpurple/plugin.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/plugin.c	Thu Nov 06 03:20:05 2008 +0000
@@ -19,6 +19,8 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+#define _PURPLE_PLUGIN_C_
+
 #include "internal.h"
 
 #include "accountopt.h"
--- a/libpurple/plugin.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/plugin.h	Thu Nov 06 03:20:05 2008 +0000
@@ -533,7 +533,7 @@
  */
 gboolean purple_plugins_enabled(void);
 
-#ifndef PURPLE_DISABLE_DEPRECATED
+#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_PLUGIN_C_)
 /**
  * Registers a function that will be called when probing is finished.
  *
@@ -544,7 +544,7 @@
 void purple_plugins_register_probe_notify_cb(void (*func)(void *), void *data);
 #endif
 
-#ifndef PURPLE_DISABLE_DEPRECATED
+#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_PLUGIN_C_)
 /**
  * Unregisters a function that would be called when probing is finished.
  *
@@ -554,7 +554,7 @@
 void purple_plugins_unregister_probe_notify_cb(void (*func)(void *));
 #endif
 
-#ifndef PURPLE_DISABLE_DEPRECATED
+#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_PLUGIN_C_)
 /**
  * Registers a function that will be called when a plugin is loaded.
  *
@@ -566,7 +566,7 @@
 										  void *data);
 #endif
 
-#ifndef PURPLE_DISABLE_DEPRECATED
+#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_PLUGIN_C_)
 /**
  * Unregisters a function that would be called when a plugin is loaded.
  *
@@ -576,7 +576,7 @@
 void purple_plugins_unregister_load_notify_cb(void (*func)(PurplePlugin *, void *));
 #endif
 
-#ifndef PURPLE_DISABLE_DEPRECATED
+#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_PLUGIN_C_)
 /**
  * Registers a function that will be called when a plugin is unloaded.
  *
@@ -588,7 +588,7 @@
 											void *data);
 #endif
 
-#ifndef PURPLE_DISABLE_DEPRECATED
+#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_PLUGIN_C_)
 /**
  * Unregisters a function that would be called when a plugin is unloaded.
  *
--- a/libpurple/plugins/autoaccept.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/plugins/autoaccept.c	Thu Nov 06 03:20:05 2008 +0000
@@ -104,7 +104,7 @@
 		return;
 	}
 
-	node = node->parent;
+	node = purple_blist_node_get_parent(node);
 	g_return_if_fail(PURPLE_BLIST_NODE_IS_CONTACT(node));
 
 	pref = purple_prefs_get_string(PREF_PATH);
@@ -179,7 +179,7 @@
 save_cb(PurpleBlistNode *node, int choice)
 {
 	if (PURPLE_BLIST_NODE_IS_BUDDY(node))
-		node = node->parent;
+		node = purple_blist_node_get_parent(node);
 	g_return_if_fail(PURPLE_BLIST_NODE_IS_CONTACT(node));
 	purple_blist_node_set_int(node, "autoaccept", choice);
 }
@@ -190,7 +190,7 @@
 	char *message;
 
 	if (PURPLE_BLIST_NODE_IS_BUDDY(node))
-		node = node->parent;
+		node = purple_blist_node_get_parent(node);
 	g_return_if_fail(PURPLE_BLIST_NODE_IS_CONTACT(node));
 
 	message = g_strdup_printf(_("When a file-transfer request arrives from %s"), 
--- a/libpurple/plugins/log_reader.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/plugins/log_reader.c	Thu Nov 06 03:20:05 2008 +0000
@@ -661,8 +661,10 @@
 		username = g_strdup(purple_normalize(account, account->username));
 	}
 
-	if (buddy)
-		savedfilename = purple_blist_node_get_string(&buddy->node, "log_reader_msn_log_filename");
+	if (buddy) {
+		savedfilename = purple_blist_node_get_string((PurpleBlistNode *)buddy,
+		                                             "log_reader_msn_log_filename");
+	}
 
 	if (savedfilename) {
 		/* As a special case, we allow the null string to kill the parsing
@@ -822,7 +824,8 @@
 	 * detected for both buddies.
 	 */
 	if (buddy && logfile) {
-		purple_blist_node_set_string(&buddy->node, "log_reader_msn_log_filename", logfile);
+		PurpleBlistNode *node = (PurpleBlistNode *)buddy;
+		purple_blist_node_set_string(node, "log_reader_msn_log_filename", logfile);
 		g_free(logfile);
 	}
 
@@ -981,8 +984,8 @@
 				gboolean from_name_matches;
 				gboolean to_name_matches;
 
-				if (buddy && buddy->alias)
-					their_name = buddy->alias;
+				if (buddy)
+					their_name = purple_buddy_get_alias(buddy);
 
 				if (log->account->alias)
 				{
@@ -1018,13 +1021,14 @@
 				} else if (to_name_matches) {
 					name_guessed = NAME_GUESS_THEM;
 				} else {
-					if (buddy && buddy->alias) {
-						char *alias = g_strdup(buddy->alias);
+					if (buddy) {
+						const char *server_alias = NULL;
+						char *alias = g_strdup(purple_buddy_get_alias(buddy));
+						char *temp;
 
 						/* "Truncate" the string at the first non-alphanumeric
 						 * character. The idea is to relax the comparison.
 						 */
-						char *temp;
 						for (temp = alias; *temp ; temp++) {
 							if (!isalnum(*temp)) {
 								*temp = '\0';
@@ -1056,9 +1060,9 @@
 							}
 						} else if (to_name_matches) {
 							name_guessed = NAME_GUESS_ME;
-						} else if (buddy->server_alias) {
+						} else if ((server_alias = purple_buddy_get_server_alias(buddy))) {
 							friendly_name_length =
-								strlen(buddy->server_alias);
+								strlen(server_alias);
 
 							/* Try to guess which user is them.
 							 * The first step is to determine if either of
@@ -1068,13 +1072,13 @@
 							 */
 							from_name_matches = (purple_str_has_prefix(
 									from_name,
-									buddy->server_alias) &&
+									server_alias) &&
 									!isalnum(*(from_name +
 									friendly_name_length)));
 
 							to_name_matches = to_name && (
 									(purple_str_has_prefix(
-									to_name, buddy->server_alias) &&
+									to_name, server_alias) &&
 									!isalnum(*(to_name +
 									friendly_name_length))));
 
@@ -1565,18 +1569,30 @@
 					g_string_append(formatted, "</b>");
 					footer = NULL;
 				} else if (strstr(line, " signed off ")) {
-					if (buddy != NULL && buddy->alias)
+					const char *alias = NULL;
+
+					if (buddy != NULL)
+						alias = purple_buddy_get_alias(buddy);
+
+					if (alias != NULL) {
 						g_string_append_printf(formatted,
-							_("%s has signed off."), buddy->alias);
-					else
+							_("%s has signed off."), alias);
+					} else {
 						g_string_append_printf(formatted,
 							_("%s has signed off."), log->name);
+					}
 					line = "";
 				} else if (strstr(line, " signed on ")) {
-					if (buddy != NULL && buddy->alias)
-						g_string_append(formatted, buddy->alias);
+					const char *alias = NULL;
+
+					if (buddy != NULL)
+						alias = purple_buddy_get_alias(buddy);
+					
+					if (alias != NULL)
+						g_string_append(formatted, alias);
 					else
 						g_string_append(formatted, log->name);
+
 					line = " logged in.";
 				} else if (purple_str_has_prefix(line,
 					"One or more messages may have been undeliverable.")) {
@@ -1631,11 +1647,15 @@
 					footer = "</span></b>";
 				}
 			} else if (purple_str_has_prefix(line, data->their_nickname)) {
-				if (buddy != NULL && buddy->alias) {
-					line += strlen(data->their_nickname) + 2;
-					g_string_append_printf(formatted,
-						"<span style=\"color: #A82F2F;\">"
-						"<b>%s</b></span>: ", buddy->alias);
+				if (buddy != NULL) {
+					const char *alias = purple_buddy_get_alias(buddy);
+
+					if (alias != NULL) {
+						line += strlen(data->their_nickname) + 2;
+						g_string_append_printf(formatted,
+							"<span style=\"color: #A82F2F;\">"
+							"<b>%s</b></span>: ", alias);
+					}
 				}
 			} else {
 				const char *line2 = strstr(line, ":");
@@ -2001,10 +2021,14 @@
 					g_string_append(formatted, "</font> ");
 
 					if (is_in_message) {
-						if (buddy_name != NULL && buddy != NULL && buddy->alias) {
+						const char *alias = NULL;
+
+						if (buddy_name != NULL && buddy != NULL &&
+						    (alias = purple_buddy_get_alias(buddy)))
+						{
 							g_string_append_printf(formatted,
 								"<span style=\"color: #A82F2F;\">"
-								"<b>%s</b></span>: ", buddy->alias);
+								"<b>%s</b></span>: ", alias);
 						}
 					} else {
 						const char *acct_name;
--- a/libpurple/plugins/perl/common/BuddyList.xs	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/plugins/perl/common/BuddyList.xs	Thu Nov 06 03:20:05 2008 +0000
@@ -1,3 +1,4 @@
+#undef PURPLE_DISABLE_DEPRECATED
 #include "module.h"
 #include "../perl-handlers.h"
 
--- a/libpurple/plugins/perl/common/module.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/plugins/perl/common/module.h	Thu Nov 06 03:20:05 2008 +0000
@@ -1,4 +1,6 @@
-
+/* Allow the Perl code to see deprecated functions, so we can continue to
+ * export them to Perl plugins. */
+#undef PURPLE_DISABLE_DEPRECATED
 
 typedef struct group *Purple__Group;
 
--- a/libpurple/plugins/signals-test.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/plugins/signals-test.c	Thu Nov 06 03:20:05 2008 +0000
@@ -116,8 +116,9 @@
                         PurpleStatus *status, void *data)
 {
 	purple_debug_misc("signals test", "buddy-status-changed (%s %s to %s)\n",
-	                buddy->name, purple_status_get_id(old_status),
-	                purple_status_get_id(status));
+	                  purple_buddy_get_name(buddy),
+	                  purple_status_get_id(old_status),
+	                  purple_status_get_id(status));
 }
 
 static void
@@ -125,25 +126,29 @@
                       void *data)
 {
 	purple_debug_misc("signals test", "buddy-idle-changed (%s %s)\n",
-	                buddy->name, old_idle ? "unidled" : "idled");
+	                  purple_buddy_get_name(buddy),
+	                  old_idle ? "unidled" : "idled");
 }
 
 static void
 buddy_signed_on_cb(PurpleBuddy *buddy, void *data)
 {
-	purple_debug_misc("signals test", "buddy-signed-on (%s)\n", buddy->name);
+	purple_debug_misc("signals test", "buddy-signed-on (%s)\n",
+	                  purple_buddy_get_name(buddy));
 }
 
 static void
 buddy_signed_off_cb(PurpleBuddy *buddy, void *data)
 {
-	purple_debug_misc("signals test", "buddy-signed-off (%s)\n", buddy->name);
+	purple_debug_misc("signals test", "buddy-signed-off (%s)\n",
+	                  purple_buddy_get_name(buddy));
 }
 
 static void
 buddy_added_cb(PurpleBuddy *buddy, void *data)
 {
-	purple_debug_misc("signals test", "buddy_added_cb (%s)\n", purple_buddy_get_name(buddy));
+	purple_debug_misc("signals test", "buddy_added_cb (%s)\n",
+	                  purple_buddy_get_name(buddy));
 }
 
 static void
@@ -160,17 +165,27 @@
 	PurpleChat *c = (PurpleChat *)node;
 	PurpleGroup *g = (PurpleGroup *)node;
 
-	if (PURPLE_BLIST_NODE_IS_CONTACT(node))
-		purple_debug_misc("signals test", "blist-node-aliased (Contact: %s, %s)\n", p->alias, old_alias);
-	else if (PURPLE_BLIST_NODE_IS_BUDDY(node))
-		purple_debug_misc("signals test", "blist-node-aliased (Buddy: %s, %s)\n", b->name, old_alias);
-	else if (PURPLE_BLIST_NODE_IS_CHAT(node))
-		purple_debug_misc("signals test", "blist-node-aliased (Chat: %s, %s)\n", c->alias, old_alias);
-	else if (PURPLE_BLIST_NODE_IS_GROUP(node))
-		purple_debug_misc("signals test", "blist-node-aliased (Group: %s, %s)\n", g->name, old_alias);
-	else
-		purple_debug_misc("signals test", "blist-node-aliased (UNKNOWN: %d, %s)\n", node->type, old_alias);
-
+	if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
+		purple_debug_misc("signals test",
+		                  "blist-node-aliased (Contact: %s, %s)\n",
+		                  purple_contact_get_alias(p), old_alias);
+	} else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		purple_debug_misc("signals test",
+		                  "blist-node-aliased (Buddy: %s, %s)\n",
+		                  purple_buddy_get_name(b), old_alias);
+	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
+		purple_debug_misc("signals test",
+		                  "blist-node-aliased (Chat: %s, %s)\n",
+		                  purple_chat_get_name(c), old_alias);
+	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
+		purple_debug_misc("signals test",
+		                  "blist-node-aliased (Group: %s, %s)\n",
+		                  purple_group_get_name(g), old_alias);
+	} else {
+		purple_debug_misc("signals test",
+		                  "blist-node-aliased (UNKNOWN: %d, %s)\n",
+		                  purple_blist_node_get_type(node), old_alias);
+	}
 }
 
 static void
@@ -181,17 +196,27 @@
 	PurpleChat *c = (PurpleChat *)node;
 	PurpleGroup *g = (PurpleGroup *)node;
 
-	if (PURPLE_BLIST_NODE_IS_CONTACT(node))
-		purple_debug_misc("signals test", "blist-node-extended-menu (Contact: %s)\n", p->alias);
-	else if (PURPLE_BLIST_NODE_IS_BUDDY(node))
-		purple_debug_misc("signals test", "blist-node-extended-menu (Buddy: %s)\n", b->name);
-	else if (PURPLE_BLIST_NODE_IS_CHAT(node))
-		purple_debug_misc("signals test", "blist-node-extended-menu (Chat: %s)\n", c->alias);
-	else if (PURPLE_BLIST_NODE_IS_GROUP(node))
-		purple_debug_misc("signals test", "blist-node-extended-menu (Group: %s)\n", g->name);
-	else
-		purple_debug_misc("signals test", "blist-node-extended-menu (UNKNOWN: %d)\n", node->type);
-
+	if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
+		purple_debug_misc("signals test",
+		                  "blist-node-extended-menu (Contact: %s)\n",
+		                  purple_contact_get_alias(p));
+	} else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		purple_debug_misc("signals test",
+		                  "blist-node-extended-menu (Buddy: %s)\n",
+		                  purple_buddy_get_name(b));
+	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
+		purple_debug_misc("signals test",
+		                  "blist-node-extended-menu (Chat: %s)\n",
+		                  purple_chat_get_name(c));
+	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
+		purple_debug_misc("signals test",
+		                  "blist-node-extended-menu (Group: %s)\n",
+		                  purple_group_get_name(g));
+	} else {
+		purple_debug_misc("signals test",
+		                  "blist-node-extended-menu (UNKNOWN: %d)\n",
+		                  purple_blist_node_get_type(node));
+	}
 }
 
 
--- a/libpurple/plugins/ssl/Makefile.am	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/plugins/ssl/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -9,10 +9,31 @@
 
 if PLUGINS
 
+# I'm sorry to report that Automake Conditionals don't support
+#   if USE_GNUTLS && USE_NSS
+# but only support testing a single variable. Hence:
+
+if USE_GNUTLS
+if USE_NSS
 plugin_LTLIBRARIES = \
 	ssl.la           \
 	ssl-gnutls.la    \
 	ssl-nss.la
+else
+plugin_LTLIBRARIES = \
+	ssl.la           \
+	ssl-gnutls.la
+endif
+else
+if USE_NSS
+plugin_LTLIBRARIES = \
+	ssl.la           \
+	ssl-nss.la
+else
+plugin_LTLIBRARIES = \
+	ssl.la
+endif
+endif
 
 ssl_la_SOURCES        = ssl.c
 ssl_gnutls_la_SOURCES = ssl-gnutls.c
--- a/libpurple/plugins/ssl/ssl-gnutls.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/plugins/ssl/ssl-gnutls.c	Thu Nov 06 03:20:05 2008 +0000
@@ -29,8 +29,6 @@
 
 #define SSL_GNUTLS_PLUGIN_ID "ssl-gnutls"
 
-#ifdef HAVE_GNUTLS
-
 #include <gnutls/gnutls.h>
 #include <gnutls/x509.h>
 
@@ -943,12 +941,9 @@
 	NULL
 };
 
-#endif /* HAVE_GNUTLS */
-
 static gboolean
 plugin_load(PurplePlugin *plugin)
 {
-#ifdef HAVE_GNUTLS
 	if(!purple_ssl_get_ops()) {
 		purple_ssl_set_ops(&ssl_ops);
 	}
@@ -960,21 +955,16 @@
 	purple_certificate_register_scheme( &x509_gnutls );
 
 	return TRUE;
-#else
-	return FALSE;
-#endif
 }
 
 static gboolean
 plugin_unload(PurplePlugin *plugin)
 {
-#ifdef HAVE_GNUTLS
 	if(purple_ssl_get_ops() == &ssl_ops) {
 		purple_ssl_set_ops(NULL);
 	}
 
 	purple_certificate_unregister_scheme( &x509_gnutls );
-#endif
 
 	return TRUE;
 }
--- a/libpurple/plugins/ssl/ssl-nss.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/plugins/ssl/ssl-nss.c	Thu Nov 06 03:20:05 2008 +0000
@@ -29,8 +29,6 @@
 
 #define SSL_NSS_PLUGIN_ID "ssl-nss"
 
-#ifdef HAVE_NSS
-
 #undef HAVE_LONG_LONG /* Make Mozilla less angry. If angry, Mozilla SMASH! */
 
 #include <nspr.h>
@@ -759,7 +757,7 @@
 	crt_dat = X509_NSS_DATA(crt);
 	g_return_val_if_fail(crt_dat, NULL);
 
-	return g_strdup(crt_dat->subjectName);
+	return g_strdup(crt_dat->issuerName);
 }
 
 static gchar *
@@ -891,13 +889,10 @@
 	NULL
 };
 
-#endif /* HAVE_NSS */
-
 
 static gboolean
 plugin_load(PurplePlugin *plugin)
 {
-#ifdef HAVE_NSS
 	if (!purple_ssl_get_ops()) {
 		purple_ssl_set_ops(&ssl_ops);
 	}
@@ -909,22 +904,17 @@
 	purple_certificate_register_scheme(&x509_nss);
 
 	return TRUE;
-#else
-	return FALSE;
-#endif
 }
 
 static gboolean
 plugin_unload(PurplePlugin *plugin)
 {
-#ifdef HAVE_NSS
 	if (purple_ssl_get_ops() == &ssl_ops) {
 		purple_ssl_set_ops(NULL);
 	}
 
 	/* Unregister our X.509 functions */
 	purple_certificate_unregister_scheme(&x509_nss);
-#endif
 
 	return TRUE;
 }
--- a/libpurple/plugins/statenotify.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/plugins/statenotify.c	Thu Nov 06 03:20:05 2008 +0000
@@ -15,13 +15,18 @@
 static void
 write_status(PurpleBuddy *buddy, const char *message)
 {
+	PurpleAccount *account = NULL;
 	PurpleConversation *conv;
 	const char *who;
 	char buf[256];
 	char *escaped;
+	const gchar *buddy_name = NULL;
+
+	account = purple_buddy_get_account(buddy);
+	buddy_name = purple_buddy_get_name(buddy);
 
 	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
-											   buddy->name, buddy->account);
+												 buddy_name, account);
 
 	if (conv == NULL)
 		return;
--- a/libpurple/plugins/tcl/tcl_cmds.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/plugins/tcl/tcl_cmds.c	Thu Nov 06 03:20:05 2008 +0000
@@ -414,7 +414,7 @@
 	Tcl_Obj *list, *tclgroup, *tclgrouplist, *tclcontact, *tclcontactlist, *tclbud, **elems, *result;
 	const char *cmds[] = { "alias", "handle", "info", "list", NULL };
 	enum { CMD_BUDDY_ALIAS, CMD_BUDDY_HANDLE, CMD_BUDDY_INFO, CMD_BUDDY_LIST } cmd;
-	PurpleBuddyList *blist;
+	PurpleBlistNodeType type;
 	PurpleBlistNode *node, *gnode, *bnode;
 	PurpleAccount *account;
 	PurpleBuddy *bud;
@@ -438,10 +438,11 @@
 			return error;
 		if ((node = tcl_list_to_buddy(interp, count, elems)) == NULL)
 			return TCL_ERROR;
-		if (node->type == PURPLE_BLIST_CHAT_NODE)
+		type = purple_blist_node_get_type(node);
+		if (type == PURPLE_BLIST_CHAT_NODE)
 			Tcl_SetObjResult(interp,
-					 Tcl_NewStringObj(((PurpleChat *)node)->alias, -1));
-		else if (node->type == PURPLE_BLIST_BUDDY_NODE)
+					 Tcl_NewStringObj(purple_chat_get_name((PurpleChat *)node), -1));
+		else if (type == PURPLE_BLIST_BUDDY_NODE)
 			Tcl_SetObjResult(interp,
                                          Tcl_NewStringObj((char *)purple_buddy_get_alias((PurpleBuddy *)node), -1));
 		return TCL_OK;
@@ -494,15 +495,17 @@
 			}
 		}
 		list = Tcl_NewListObj(0, NULL);
-		blist = purple_get_blist();
-		for (gnode = blist->root; gnode != NULL; gnode = gnode->next) {
+		for (gnode = purple_blist_get_root(); gnode != NULL; gnode = purple_blist_node_get_sibling_next(gnode)) {
 			tclgroup = Tcl_NewListObj(0, NULL);
 			Tcl_ListObjAppendElement(interp, tclgroup, Tcl_NewStringObj("group", -1));
 			Tcl_ListObjAppendElement(interp, tclgroup,
-						 Tcl_NewStringObj(((PurpleGroup *)gnode)->name, -1));
+						 Tcl_NewStringObj(purple_group_get_name((PurpleGroup *)gnode), -1));
 			tclgrouplist = Tcl_NewListObj(0, NULL);
-			for (node = gnode->child; node != NULL; node = node->next) {
-				switch (node->type) {
+			for (node = purple_blist_node_get_first_child(gnode); node != NULL; node = purple_blist_node_get_sibling_next(node)) {
+				PurpleAccount *account;
+
+				type = purple_blist_node_get_type(node);
+				switch (type) {
 				case PURPLE_BLIST_CONTACT_NODE:
 					tclcontact = Tcl_NewListObj(0, NULL);
 					Tcl_IncrRefCount(tclcontact);
@@ -510,17 +513,18 @@
 					tclcontactlist = Tcl_NewListObj(0, NULL);
 					Tcl_IncrRefCount(tclcontactlist);
 					count = 0;
-					for (bnode = node->child; bnode != NULL; bnode = bnode ->next) {
-						if (bnode->type != PURPLE_BLIST_BUDDY_NODE)
+					for (bnode = purple_blist_node_get_first_child(node); bnode != NULL; bnode = purple_blist_node_get_sibling_next(bnode)) {
+						if (purple_blist_node_get_type(bnode) != PURPLE_BLIST_BUDDY_NODE)
 							continue;
 						bud = (PurpleBuddy *)bnode;
-						if (!all && !purple_account_is_connected(bud->account))
+						account = purple_buddy_get_account(bud);
+						if (!all && !purple_account_is_connected(account))
 							continue;
 						count++;
 						tclbud = Tcl_NewListObj(0, NULL);
 						Tcl_ListObjAppendElement(interp, tclbud, Tcl_NewStringObj("buddy", -1));
-						Tcl_ListObjAppendElement(interp, tclbud, Tcl_NewStringObj(bud->name, -1));
-						Tcl_ListObjAppendElement(interp, tclbud, purple_tcl_ref_new(PurpleTclRefAccount, bud->account));
+						Tcl_ListObjAppendElement(interp, tclbud, Tcl_NewStringObj(purple_buddy_get_name(bud), -1));
+						Tcl_ListObjAppendElement(interp, tclbud, purple_tcl_ref_new(PurpleTclRefAccount, account));
 						Tcl_ListObjAppendElement(interp, tclcontactlist, tclbud);
 					}
 					if (count) {
@@ -532,16 +536,17 @@
 					break;
 				case PURPLE_BLIST_CHAT_NODE:
 					cnode = (PurpleChat *)node;
-					if (!all && !purple_account_is_connected(cnode->account))
+					account = purple_chat_get_account(cnode);
+					if (!all && !purple_account_is_connected(account))
 						continue;
 					tclbud = Tcl_NewListObj(0, NULL);
 					Tcl_ListObjAppendElement(interp, tclbud, Tcl_NewStringObj("chat", -1));
-					Tcl_ListObjAppendElement(interp, tclbud, Tcl_NewStringObj(cnode->alias, -1));
-					Tcl_ListObjAppendElement(interp, tclbud, purple_tcl_ref_new(PurpleTclRefAccount, cnode->account));
+					Tcl_ListObjAppendElement(interp, tclbud, Tcl_NewStringObj(purple_chat_get_name(cnode), -1));
+					Tcl_ListObjAppendElement(interp, tclbud, purple_tcl_ref_new(PurpleTclRefAccount, account));
 					Tcl_ListObjAppendElement(interp, tclgrouplist, tclbud);
 					break;
 				default:
-					purple_debug(PURPLE_DEBUG_WARNING, "tcl", "Unexpected buddy type %d", node->type);
+					purple_debug(PURPLE_DEBUG_WARNING, "tcl", "Unexpected buddy type %d", type);
 					continue;
 				}
 			}
--- a/libpurple/plugins/tcl/tcl_signals.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/plugins/tcl/tcl_signals.c	Thu Nov 06 03:20:05 2008 +0000
@@ -292,13 +292,13 @@
 					node = *va_arg(args, PurpleBlistNode **);
 				else
 					node = va_arg(args, PurpleBlistNode *);
-				switch (node->type) {
+				switch (purple_blist_node_get_type(node)) {
 				case PURPLE_BLIST_GROUP_NODE:
 					arg = Tcl_NewListObj(0, NULL);
 					Tcl_ListObjAppendElement(handler->interp, arg,
 								 Tcl_NewStringObj("group", -1));
 					Tcl_ListObjAppendElement(handler->interp, arg,
-								 Tcl_NewStringObj(((PurpleGroup *)node)->name, -1));
+								 Tcl_NewStringObj(purple_group_get_name((PurpleGroup *)node), -1));
 					break;
 				case PURPLE_BLIST_CONTACT_NODE:
 					/* g_string_printf(val, "contact {%s}", Contact Name? ); */
@@ -309,20 +309,20 @@
 					Tcl_ListObjAppendElement(handler->interp, arg,
 								 Tcl_NewStringObj("buddy", -1));
 					Tcl_ListObjAppendElement(handler->interp, arg,
-								 Tcl_NewStringObj(((PurpleBuddy *)node)->name, -1));
+								 Tcl_NewStringObj(purple_buddy_get_name((PurpleBuddy *)node), -1));
 					Tcl_ListObjAppendElement(handler->interp, arg,
 								 purple_tcl_ref_new(PurpleTclRefAccount,
-										  ((PurpleBuddy *)node)->account));
+										    purple_buddy_get_account((PurpleBuddy *)node)));
 					break;
 				case PURPLE_BLIST_CHAT_NODE:
 					arg = Tcl_NewListObj(0, NULL);
 					Tcl_ListObjAppendElement(handler->interp, arg,
 								 Tcl_NewStringObj("chat", -1));
 					Tcl_ListObjAppendElement(handler->interp, arg,
-								 Tcl_NewStringObj(((PurpleChat *)node)->alias, -1));
+								 Tcl_NewStringObj(purple_chat_get_name((PurpleChat *)node), -1));
 					Tcl_ListObjAppendElement(handler->interp, arg,
 								 purple_tcl_ref_new(PurpleTclRefAccount,
-										  ((PurpleChat *)node)->account));
+										  purple_chat_get_account((PurpleChat *)node)));
 					break;
 				case PURPLE_BLIST_OTHER_NODE:
 					arg = Tcl_NewStringObj("other", -1);
--- a/libpurple/protocols/bonjour/bonjour.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Thu Nov 06 03:20:05 2008 +0000
@@ -59,18 +59,18 @@
 		return;
 
 	/* Go through and remove all buddies that belong to this account */
-	for (cnode = ((PurpleBlistNode *) bonjour_group)->child; cnode; cnode = cnodenext) {
-		cnodenext = cnode->next;
+	for (cnode = purple_blist_node_get_first_child((PurpleBlistNode *) bonjour_group); cnode; cnode = cnodenext) {
+		cnodenext = purple_blist_node_get_sibling_next(cnode);
 		if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
 			continue;
-		for (bnode = cnode->child; bnode; bnode = bnodenext) {
-			bnodenext = bnode->next;
+		for (bnode = purple_blist_node_get_first_child(cnode); bnode; bnode = bnodenext) {
+			bnodenext = purple_blist_node_get_sibling_next(bnode);
 			if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
 				continue;
 			buddy = (PurpleBuddy *) bnode;
-			if (buddy->account != account)
+			if (purple_buddy_get_account(buddy) != account)
 				continue;
-			purple_prpl_got_user_status(account, buddy->name, "offline", NULL);
+			purple_prpl_got_user_status(account, purple_buddy_get_name(buddy), "offline", NULL);
 			purple_account_remove_buddy(account, buddy, NULL);
 			purple_blist_remove_buddy(buddy);
 		}
--- a/libpurple/protocols/bonjour/buddy.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/bonjour/buddy.c	Thu Nov 06 03:20:05 2008 +0000
@@ -127,7 +127,7 @@
 {
 	PurpleGroup *group;
 	PurpleAccount *account = bonjour_buddy->account;
-	const char *status_id, *old_hash, *new_hash;
+	const char *status_id, *old_hash, *new_hash, *name;
 
 	/* Translate between the Bonjour status and the Purple status */
 	if (bonjour_buddy->status != NULL && g_ascii_strcasecmp("dnd", bonjour_buddy->status) == 0)
@@ -158,10 +158,11 @@
 	}
 
 	buddy->proto_data = bonjour_buddy;
+	name = purple_buddy_get_name(buddy);
 
 	/* Create the alias for the buddy using the first and the last name */
 	if (bonjour_buddy->nick)
-		serv_got_alias(purple_account_get_connection(account), buddy->name, bonjour_buddy->nick);
+		serv_got_alias(purple_account_get_connection(account), name, bonjour_buddy->nick);
 	else {
 		gchar *alias = NULL;
 		const char *first, *last;
@@ -172,18 +173,18 @@
 						(first && *first ? first : ""),
 						(first && *first && last && *last ? " " : ""),
 						(last && *last ? last : ""));
-		serv_got_alias(purple_account_get_connection(account), buddy->name, alias);
+		serv_got_alias(purple_account_get_connection(account), name, alias);
 		g_free(alias);
 	}
 
 	/* Set the user's status */
 	if (bonjour_buddy->msg != NULL)
-		purple_prpl_got_user_status(account, buddy->name, status_id,
+		purple_prpl_got_user_status(account, name, status_id,
 					    "message", bonjour_buddy->msg, NULL);
 	else
-		purple_prpl_got_user_status(account, buddy->name, status_id, NULL);
+		purple_prpl_got_user_status(account, name, status_id, NULL);
 
-	purple_prpl_got_user_idle(account, buddy->name, FALSE, 0);
+	purple_prpl_got_user_idle(account, name, FALSE, 0);
 
 	/* TODO: Because we don't save Bonjour buddies in blist.xml,
 	 * we will always have to look up the buddy icon at login time.
@@ -198,7 +199,7 @@
 		 * as what we looked up. */
 		bonjour_dns_sd_retrieve_buddy_icon(bonjour_buddy);
 	} else if (!new_hash)
-		purple_buddy_icons_set_for_user(account, buddy->name, NULL, 0, NULL);
+		purple_buddy_icons_set_for_user(account, name, NULL, 0, NULL);
 }
 
 /**
--- a/libpurple/protocols/bonjour/jabber.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/bonjour/jabber.c	Thu Nov 06 03:20:05 2008 +0000
@@ -142,7 +142,7 @@
 _jabber_parse_and_write_message_to_ui(xmlnode *message_node, PurpleBuddy *pb)
 {
 	xmlnode *body_node, *html_node, *events_node;
-	PurpleConnection *gc = pb->account->gc;
+	PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(pb));
 	gchar *body = NULL;
 	gboolean composing_event = FALSE;
 
@@ -225,7 +225,7 @@
 	}
 
 	/* Send the message to the UI */
-	serv_got_im(gc, pb->name, body, 0, time(NULL));
+	serv_got_im(gc, purple_buddy_get_name(pb), body, 0, time(NULL));
 
 	g_free(body);
 }
--- a/libpurple/protocols/gg/gg.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/gg/gg.c	Thu Nov 06 03:20:05 2008 +0000
@@ -421,7 +421,7 @@
 	 */
 
 	/* Need to disconnect or actually log in. For now, we disconnect. */
-	purple_connection_destroy(gc);
+	purple_account_disconnect(account);
 
 exit_err:
 	if(account->registration_cb)
@@ -446,7 +446,7 @@
 	GGPInfo *info = gc->proto_data;
 	GGPToken *token = info->token;
 
-	purple_connection_destroy(gc);
+	purple_account_disconnect(gc->account);
 
 	g_free(token->id);
 	g_free(token->data);
--- a/libpurple/protocols/irc/irc.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/irc/irc.c	Thu Nov 06 03:20:05 2008 +0000
@@ -62,8 +62,6 @@
 
 PurplePlugin *_irc_plugin = NULL;
 
-static const char *status_chars = "@+%&";
-
 static void irc_view_motd(PurplePluginAction *action)
 {
 	PurpleConnection *gc = (PurpleConnection *) action->context;
@@ -518,10 +516,7 @@
 	char *plain;
 	const char *args[2];
 
-	if (strchr(status_chars, *who) != NULL)
-		args[0] = who + 1;
-	else
-		args[0] = who;
+	args[0] = irc_nick_skip_mode(irc, who);
 
 	purple_markup_html_to_xhtml(what, NULL, &plain);
 	args[1] = plain;
--- a/libpurple/protocols/irc/irc.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/irc/irc.h	Thu Nov 06 03:20:05 2008 +0000
@@ -109,6 +109,8 @@
 char *irc_mirc2html(const char *string);
 char *irc_mirc2txt(const char *string);
 
+const char *irc_nick_skip_mode(struct irc_conn *irc, const char *string);
+
 gboolean irc_ischannel(const char *string);
 
 void irc_register_commands(void);
--- a/libpurple/protocols/irc/parse.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/irc/parse.c	Thu Nov 06 03:20:05 2008 +0000
@@ -497,6 +497,19 @@
         return result;
 }
 
+const char *irc_nick_skip_mode(struct irc_conn *irc, const char *nick)
+{
+	static const char *default_modes = "@+%&";
+	const char *mode_chars;
+
+	mode_chars = irc->mode_chars ? irc->mode_chars : default_modes;
+
+	while (strchr(mode_chars, *nick) != NULL)
+		nick++;
+
+	return nick;
+}
+
 gboolean irc_ischannel(const char *string)
 {
 	return (string[0] == '#' || string[0] == '&');
--- a/libpurple/protocols/jabber/iq.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/jabber/iq.c	Thu Nov 06 03:20:05 2008 +0000
@@ -106,8 +106,7 @@
 
 void jabber_iq_set_id(JabberIq *iq, const char *id)
 {
-	if(iq->id)
-		g_free(iq->id);
+	g_free(iq->id);
 
 	if(id) {
 		xmlnode_set_attrib(iq->node, "id", id);
@@ -321,9 +320,42 @@
 	from = xmlnode_get_attrib(packet, "from");
 	id = xmlnode_get_attrib(packet, "id");
 
+	if(type == NULL || !(!strcmp(type, "get") || !strcmp(type, "set")
+			|| !strcmp(type, "result") || !strcmp(type, "error"))) {
+		purple_debug_error("jabber", "IQ with invalid type ('%s') - ignoring.\n",
+						   type ? type : "(null)");
+		return;
+	}
+
+	/* All IQs must have an ID, so send an error for a set/get that doesn't */
+	if(!id || !*id) {
+
+		if(!strcmp(type, "set") || !strcmp(type, "get")) {
+			JabberIq *iq = jabber_iq_new(js, JABBER_IQ_ERROR);
+
+			xmlnode_free(iq->node);
+			iq->node = xmlnode_copy(packet);
+			xmlnode_set_attrib(iq->node, "to", from);
+			xmlnode_remove_attrib(iq->node, "from");
+			xmlnode_set_attrib(iq->node, "type", "error");
+			/* This id is clearly not useful, but we must put something there for a valid stanza */
+			iq->id = jabber_get_next_id(js);
+			xmlnode_set_attrib(iq->node, "id", iq->id);
+			error = xmlnode_new_child(iq->node, "error");
+			xmlnode_set_attrib(error, "type", "modify");
+			x = xmlnode_new_child(error, "bad-request");
+			xmlnode_set_namespace(x, "urn:ietf:params:xml:ns:xmpp-stanzas");
+
+			jabber_iq_send(iq);
+		} else
+			purple_debug_error("jabber", "IQ of type '%s' missing id - ignoring.\n", type);
+
+		return;
+	}
+
 	/* First, lets see if a special callback got registered */
 
-	if(type && (!strcmp(type, "result") || !strcmp(type, "error"))) {
+	if(!strcmp(type, "result") || !strcmp(type, "error")) {
 		if(id && *id && (jcd = g_hash_table_lookup(js->iq_callbacks, id))) {
 			jcd->callback(js, packet, jcd->data);
 			jabber_iq_remove_callback_by_id(js, id);
@@ -333,7 +365,7 @@
 
 	/* Apparently not, so lets see if we have a pre-defined handler */
 
-	if(type && query && (xmlns = xmlnode_get_namespace(query))) {
+	if(query && (xmlns = xmlnode_get_namespace(query))) {
 		if((jih = g_hash_table_lookup(iq_handlers, xmlns))) {
 			jih(js, packet);
 			return;
@@ -354,7 +386,7 @@
 		jabber_gmail_poke(js, packet);
 		return;
 	}
-	
+
 	purple_debug_info("jabber", "jabber_iq_parse\n");
 
 	if(xmlnode_get_child_with_namespace(packet, "ping", "urn:xmpp:ping")) {
@@ -375,7 +407,7 @@
 #endif
 
 	/* If we get here, send the default error reply mandated by XMPP-CORE */
-	if(type && (!strcmp(type, "set") || !strcmp(type, "get"))) {
+	if(!strcmp(type, "set") || !strcmp(type, "get")) {
 		JabberIq *iq = jabber_iq_new(js, JABBER_IQ_ERROR);
 
 		xmlnode_free(iq->node);
--- a/libpurple/protocols/msn/command.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/msn/command.h	Thu Nov 06 03:20:05 2008 +0000
@@ -51,6 +51,7 @@
 	size_t payload_len;
 
 	MsnPayloadCb payload_cb;
+	void *payload_cbdata;
 };
 
 MsnCommand *msn_command_from_string(const char *string);
--- a/libpurple/protocols/msn/notification.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/msn/notification.c	Thu Nov 06 03:20:05 2008 +0000
@@ -578,7 +578,7 @@
 		g_snprintf(fmt_str, sizeof(fmt_str), "%d", MSN_NETWORK_PASSPORT);
 
 	/*mobile*/
-	//type_str = g_strdup_printf("4");
+	/*type_str = g_strdup_printf("4");*/
 	xmlnode_set_attrib(c_node, "t", fmt_str);
 
 	xmlnode_insert_child(d_node, c_node);
@@ -756,21 +756,46 @@
 }
 
 static void
+adl_error_parse(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, size_t len)
+{
+	MsnSession *session;
+	PurpleAccount *account;
+	PurpleConnection *gc;
+	/*char *adl = g_strndup(payload, len);*/
+	char *reason = g_strdup_printf(_("Unknown error (%d)"),
+		GPOINTER_TO_INT(cmd->payload_cbdata)/*, adl*/);
+	/*g_free(adl);*/
+
+	session = cmdproc->session;
+	account = session->account;
+	gc = purple_account_get_connection(account);
+
+	purple_notify_error(gc, NULL, _("Unable to add user"), reason);
+	g_free(reason);
+}
+
+static void
 adl_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error)
 {
 	MsnSession *session;
 	PurpleAccount *account;
 	PurpleConnection *gc;
-	char *reason = NULL;
+	MsnCommand *cmd = cmdproc->last_cmd;
 
 	session = cmdproc->session;
 	account = session->account;
 	gc = purple_account_get_connection(account);
 
 	purple_debug_error("msn", "ADL error\n");
-	reason = g_strdup_printf(_("Unknown error (%d)"), error);
-	purple_notify_error(gc, NULL, _("Unable to add user"), reason);
-	g_free(reason);
+	if (cmd->param_count > 1) {
+		cmd->payload_cb = adl_error_parse;
+		cmd->payload_len = atoi(cmd->params[1]);
+		cmd->payload_cbdata = GINT_TO_POINTER(error);
+	} else {
+		char *reason = g_strdup_printf(_("Unknown error (%d)"), error);
+		purple_notify_error(gc, NULL, _("Unable to add user"), reason);
+		g_free(reason);
+	}
 }
 
 static void
--- a/libpurple/protocols/qq/AUTHORS	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/AUTHORS	Thu Nov 06 03:20:05 2008 +0000
@@ -35,4 +35,5 @@
 khc@pidgin.im
 qulogic@pidgin.im
 rlaager@pidgin.im
+Huang Guan	: http://home.xxsyzx.com
 OpenQ Google Group	: http://groups.google.com/group/openq
--- a/libpurple/protocols/qq/ChangeLog	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/ChangeLog	Thu Nov 06 03:20:05 2008 +0000
@@ -1,3 +1,126 @@
+2008.10.28 - flos <lonicerae(at)gmail.com>
+	* Updated AUTHORS
+
+2008.10.27 - ccpaging <ccpaging(at)gmail.com>
+	* Fixed a bug in buddy_info.c
+
+2008.10.27 - ccpaging <ccpaging(at)gmail.com>
+	* Update 'buddy_adding' protocol
+
+2008.10.22 - ccpaging <ccpaging(at)gmail.com>
+	* 20081022
+
+2008.10.20 - ccpaging <ccpaging(at)gmail.com>
+	* Support incoming authorization of 'buddy_adding' protocol of QQ2007/2008
+
+2008.10.14 - ccpaging <ccpaging(at)gmail.com>
+	* 2007 remove buddy ok
+	* Removed group_search.c/h
+
+2008.10.10 - ccpaging <ccpaging(at)gmail.com>
+	* Support part of 'buddy' protocol of QQ2007/2008
+
+2008.10.10 - ccpaging <ccpaging(at)gmail.com>
+	* Keep group_search.c/h for later use
+	* 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 
+
+2008.10.08 - ccpaging <ccpaging(at)gmail.com>
+	* Update 'group' protocol
+
+2008.10.08 - ccpaging <ccpaging(at)gmail.com>
+	* 20081008-1
+
+2008.10.08 - ccpaging <ccpaging(at)gmail.com>
+	* Update group part
+	* Delete some meaningless functions and data
+	* Added 'change my icon'
+
+2008.10.08 - lonicerae <lonicerae(at)gmail.com>
+	* Update Makefile.mingw
+
+2008.10.08 - ccpaging <ccpaging(at)gmail.com>
+	* Fixed QQ_BUDDY_ICON_DIR problem
+
+2008.10.07 - lonicerae <lonicerae(at)gmail.com>
+	* Update 'version display'
+
+2008.10.07 - lonicerae <lonicerae(at)gmail.com>
+	* Added some defensive code for 'action' series functions of qq.c
+
+2008.10.07 - ccpaging <ccpaging(at)gmail.com>
+	* Update buddy icon
+
+2008.10.07 - ccpaging <ccpaging(at)gmail.com>
+	* Update qq_buddy
+
+2008.10.07 - ccpaging <ccpaging(at)gmail.com>
+	* Update qun conversation
+
+2008.10.05 - lonicerae <lonicerae(at)gmail.com>
+	* Bug fix in 'About OpenQ' dialog
+
+2008.10.05 - lonicerae <lonicerae(at)gmail.com>
+	* Added 'About OpenQ' dialog
+
+2008.10.05 - ccpaging <ccpagint(at)gmail.com>
+	* Add my uid into buddy list
+	* Fixed a minor bug in qq_create_buddy. Not get new buddy's info.
+	* There are 38 fields in protocol 2008, one more than 2005/2007.
+	* The packet of Modifing buddy info is changed. Need sample to fix it.
+
+2008.10.04 - ccpaging <ccpagint(at)gmail.com>
+	* Update protocol for 2007
+	* Code cleanup
+
+2008.10.04 - lonicerae <lonicerae(at)gmail.com>
+	* fixed a bug in qq_base.c
+
+2008.10.03 - ccpaging <ccpaging(at)gmail.com>
+	* 2007 protocol:
+		1. fixed 'get room info'
+		2. fixed 'get buddy level'
+
+2008.10.02 - ccpaging <ccpaging(at)gmail.com>
+	* Added 'Captcha Display' function
+	* QQ2007 for openq, programed by Emil Alexiev:
+		1. Most functions from patch written by Emil Alexiev merged into trunk, except 'buddy operations'
+		2. 'online buddy status' and 'qun buddies' still have problems
+	* QQ2008 console client, programed by Shrimp:
+		1. 'before login' and 'keep alive' parts merged into trunk
+
+2008.09.30 - ccpaging <ccpaging(at)gmail.com>
+	* Successfully login using 2007/2008 protocols
+
+2008.09.29 - ccpaging <ccpaging(at)gmail.com>
+	* 'Check Password' function for protocol 2007/2008
+
+2008.09.28 - ccpaging <ccpaging(at)gmail.com>
+	* The source is only for debug, not for user:
+		1. Implement new QQ protocol 2007/2008, include login and change status
+		2. Check 2005's login reply packet, get last 3 login time.
+		3. Server's notice and news is displayed in self buddy (The new buddy created in buddy list).
+		4. The notice messages when adding/removing QQ Qun's buddy displayed in char conversation. They are displayed as purple notify windows in the past.
+		5. The notice messages when adding/removing buddy displayed in self buddy's conversation. They are displayed as purple notify windows in the past.
+		6. Client version can be selected in  account option. Now only qq2005 is working, other new version is only for debug.
+
+2008.09.26 - ccpaging <ccpaging(at)gmail.com>
+	* Added 'Request/Add/Remove Buddy' functions
+
+2008.09.19 - ccpaging <ccpaging(at)gmail.com>
+	* Rewrite buddy modify info, there is a ticket for this problem
+	* Use ship32 to trans action code between request packet and reply packet process
+
+2008.09.15 - csyfek <csyfek(at)gmail.com>
+	* im.pidgin.pidgin.openq branch
+
 2008.09.05 - ccpaging <ccpaging(at)gmail.com>
 	* Filter chars 0x01-0x20 in nickname
 
--- a/libpurple/protocols/qq/Makefile.am	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -18,12 +18,6 @@
 	file_trans.h \
 	group.c \
 	group.h \
-	group_conv.c \
-	group_conv.h \
-	group_find.c \
-	group_find.h \
-	group_free.c \
-	group_free.h \
 	group_internal.c \
 	group_internal.h \
 	group_im.c \
@@ -34,10 +28,8 @@
 	group_join.h \
 	group_opt.c \
 	group_opt.h \
-	group_search.c \
-	group_search.h \
-	header_info.c \
-	header_info.h \
+	qq_define.c \
+	qq_define.h \
 	im.c \
 	im.h \
 	qq_process.c \
@@ -54,8 +46,6 @@
 	send_file.h \
 	qq_trans.c \
 	qq_trans.h \
-	sys_msg.c \
-	sys_msg.h \
 	utils.c \
 	utils.h
 
--- a/libpurple/protocols/qq/Makefile.mingw	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/Makefile.mingw	Thu Nov 06 03:20:05 2008 +0000
@@ -6,7 +6,6 @@
 
 PIDGIN_TREE_TOP := ../../..
 include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
-
 TARGET = libqq
 TYPE = PLUGIN
 
@@ -46,16 +45,12 @@
 	qq_crypt.c \
 	file_trans.c \
 	group.c \
-	group_conv.c \
-	group_find.c \
-	group_free.c \
 	group_internal.c \
 	group_im.c \
 	group_info.c \
 	group_join.c \
 	group_opt.c \
-	group_search.c \
-	header_info.c \
+	qq_define.c \
 	im.c \
 	packet_parse.c \
 	qq.c \
@@ -64,7 +59,6 @@
 	qq_process.c \
 	qq_trans.c \
 	send_file.c \
-	sys_msg.c \
 	utils.c
 
 OBJECTS = $(C_SRC:%.c=%.o)
--- a/libpurple/protocols/qq/buddy_info.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/buddy_info.c	Thu Nov 06 03:20:05 2008 +0000
@@ -32,33 +32,11 @@
 #include "buddy_list.h"
 #include "buddy_info.h"
 #include "char_conv.h"
-#include "header_info.h"
+#include "im.h"
+#include "qq_define.h"
 #include "qq_base.h"
 #include "qq_network.h"
 
-#define QQ_PRIMARY_INFORMATION _("Primary Information")
-#define QQ_ADDITIONAL_INFORMATION _("Additional Information")
-#define QQ_INTRO _("Personal Introduction")
-#define QQ_NUMBER _("QQ Number")
-#define QQ_NICKNAME _("Nickname")
-#define QQ_NAME _("Name")
-#define QQ_AGE _("Age")
-#define QQ_GENDER _("Gender")
-#define QQ_COUNTRY _("Country/Region")
-#define QQ_PROVINCE _("Province/State")
-#define QQ_CITY _("City")
-#define QQ_HOROSCOPE _("Horoscope Symbol")
-#define QQ_OCCUPATION _("Occupation")
-#define QQ_ZODIAC _("Zodiac Sign")
-#define QQ_BLOOD _("Blood Type")
-#define QQ_COLLEGE _("College")
-#define QQ_EMAIL _("Email")
-#define QQ_ADDRESS _("Address")
-#define QQ_ZIPCODE _("Zipcode")
-#define QQ_CELL _("Cellphone Number")
-#define QQ_TELEPHONE _("Phone Number")
-#define QQ_HOMEPAGE _("Homepage")
-
 #define QQ_HOROSCOPE_SIZE 13
 static const gchar *horoscope_names[] = {
 	"-", N_("Aquarius"), N_("Pisces"), N_("Aries"), N_("Taurus"),
@@ -78,223 +56,171 @@
 	"-", "A", "B", "O", "AB", N_("Other")
 };
 
-#define QQ_GENDER_SIZE 2
+#define QQ_PUBLISH_SIZE 3
+static const gchar *publish_types[] = {
+	N_("Visible"), N_("Firend Only"), N_("Private")
+};
+
+#define QQ_GENDER_SIZE 3
 static const gchar *genders[] = {
+	N_("Private"),
 	N_("Male"),
-	N_("Female")
+	N_("Female"),
+};
+
+static const gchar *genders_zh[] = {
+	"-",
+	"\xc4\xd0",
+	"\xc5\xae",
+};
+
+#define QQ_FACES	    134
+#define QQ_ICON_PREFIX "qq_"
+#define QQ_ICON_SUFFIX ".png"
+
+enum {
+	QQ_INFO_UID = 0, QQ_INFO_NICK, QQ_INFO_COUNTRY, QQ_INFO_PROVINCE, QQ_INFO_ZIPCODE,
+	QQ_INFO_ADDR, QQ_INFO_TEL, QQ_INFO_AGE, QQ_INFO_GENDER, QQ_INFO_NAME, QQ_INFO_EMAIL,
+	QQ_INFO_PG_SN, QQ_INFO_PG_NUM, QQ_INFO_PG_SP, QQ_INFO_PG_BASE_NUM, QQ_INFO_PG_TYPE,
+	QQ_INFO_OCCU, QQ_INFO_HOME_PAGE, QQ_INFO_AUTH_TYPE, QQ_INFO_UNKNOW1, QQ_INFO_UNKNOW2,
+	QQ_INFO_FACE, QQ_INFO_MOBILE, QQ_INFO_MOBILE_TYPE, QQ_INFO_INTRO, QQ_INFO_CITY,
+	QQ_INFO_UNKNOW3, QQ_INFO_UNKNOW4, QQ_INFO_UNKNOW5,
+	QQ_INFO_IS_PUB_MOBILE, QQ_INFO_IS_PUB_CONTACT, QQ_INFO_COLLEGE, QQ_INFO_HOROSCOPE,
+	QQ_INFO_ZODIAC, QQ_INFO_BLOOD, QQ_INFO_SHOW, QQ_INFO_UNKNOW6,
+	QQ_INFO_LAST_2007, QQ_INFO_LAST,
+};
+
+enum {
+	QQ_FIELD_UNUSED = 0, QQ_FIELD_BASE, QQ_FIELD_EXT, QQ_FIELD_CONTACT, QQ_FIELD_ADDR
+};
+
+enum {
+	QQ_FIELD_LABEL = 0, QQ_FIELD_STRING, QQ_FIELD_MULTI, QQ_FIELD_BOOL, QQ_FIELD_CHOICE,
 };
 
-#define QQ_CONTACT_FIELDS                               37
-#define QQ_FACES	    100
-
-/* There is no user id stored in the reply packet for information query
- * we have to manually store the query, so that we know the query source */
-typedef struct _qq_info_query {
-	guint32 uid;
-	gboolean show_window;
-	gboolean modify_info;
-} qq_info_query;
+typedef struct {
+	int iclass;
+	int type;
+	char *id;
+	char *text;
+	const gchar **choice;
+	int choice_size;
+} QQ_FIELD_INFO;
 
-typedef struct _contact_info {
-	gchar *uid;
-	gchar *nick;
-	gchar *country;
-	gchar *province;
-	gchar *zipcode;
-	gchar *address;
-	gchar *tel;
-	gchar *age;
-	gchar *gender;
-	gchar *name;
-	gchar *email;
-	gchar *pager_sn;
-	gchar *pager_num;
-	gchar *pager_sp;
-	gchar *pager_base_num;
-	gchar *pager_type;
-	gchar *occupation;
-	gchar *homepage;
-	gchar *auth_type;
-	gchar *unknown1;
-	gchar *unknown2;
-	gchar *face;
-	gchar *hp_num;
-	gchar *hp_type;
-	gchar *intro;
-	gchar *city;
-	gchar *unknown3;
-	gchar *unknown4;
-	gchar *unknown5;
-	gchar *is_open_hp;
-	gchar *is_open_contact;
-	gchar *college;
-	gchar *horoscope;
-	gchar *zodiac;
-	gchar *blood;
-	gchar *qq_show;
-	gchar *unknown6;        /* always 0x2D */
-} contact_info;
+static const QQ_FIELD_INFO field_infos[] = {
+	{ QQ_FIELD_BASE, 		QQ_FIELD_STRING, "uid", 			N_("QQ Number"), NULL, 0 },
+	{ QQ_FIELD_BASE, 		QQ_FIELD_STRING, "nick", 			N_("Nickname"), NULL, 0 },
+	{ QQ_FIELD_ADDR, 		QQ_FIELD_STRING, "country", 	N_("Country/Region"), NULL, 0 },
+	{ QQ_FIELD_ADDR, 		QQ_FIELD_STRING, "province", 	N_("Province/State"), NULL, 0 },
+	{ QQ_FIELD_ADDR, 		QQ_FIELD_STRING, "zipcode", 	N_("Zipcode"), NULL, 0 },
+	{ QQ_FIELD_ADDR, 		QQ_FIELD_STRING, "address", 	N_("Address"), NULL, 0 },
+	{ QQ_FIELD_CONTACT, QQ_FIELD_STRING, "tel", 				N_("Phone Number"), NULL, 0 },
+	{ QQ_FIELD_BASE, 		QQ_FIELD_STRING, "age", 			N_("Age"), NULL, 0 },
+	{ QQ_FIELD_BASE, 		QQ_FIELD_CHOICE, "gender", 		N_("Gender"), genders, QQ_GENDER_SIZE },
+	{ QQ_FIELD_BASE, 		QQ_FIELD_STRING, "name", 			N_("Name"), NULL, 0 },
+	{ QQ_FIELD_CONTACT, QQ_FIELD_STRING, "email", 			N_("Email"), NULL, 0 },
+	{ QQ_FIELD_UNUSED, 	QQ_FIELD_STRING, "pg_sn",		"Pager Serial Num", NULL, 0 },
+	{ QQ_FIELD_UNUSED, 	QQ_FIELD_STRING, "pg_num",	"Pager Num", NULL, 0 },
+	{ QQ_FIELD_UNUSED, 	QQ_FIELD_STRING, "pg_sp",		"Pager Serivce Provider", NULL, 0 },
+	{ QQ_FIELD_UNUSED, 	QQ_FIELD_STRING, "pg_sta",		"Pager Station Num", NULL, 0 },
+	{ QQ_FIELD_UNUSED, 	QQ_FIELD_STRING, "pg_type",	"Pager Type", NULL, 0 },
+	{ QQ_FIELD_BASE, 		QQ_FIELD_STRING, "occupation", 	N_("Occupation"), NULL, 0 },
+	{ QQ_FIELD_CONTACT, QQ_FIELD_STRING, "homepage", 		N_("Homepage"), NULL, 0 },
+	{ QQ_FIELD_BASE, 		QQ_FIELD_BOOL, 	"auth", 				N_("Authorize adding"), NULL, 0 },
+	{ QQ_FIELD_UNUSED, 	QQ_FIELD_STRING, "unknow1",	"Unknow1", NULL, 0 },
+	{ QQ_FIELD_UNUSED, 	QQ_FIELD_STRING, "unknow2",	"Unknow2", NULL, 0 },
+	{ QQ_FIELD_UNUSED, 		QQ_FIELD_STRING, "face",				"Face", NULL, 0 },
+	{ QQ_FIELD_CONTACT, QQ_FIELD_STRING, "mobile",		N_("Cellphone Number"), NULL, 0 },
+	{ QQ_FIELD_UNUSED, 	QQ_FIELD_STRING, "mobile_type","Cellphone Type", NULL, 0 },
+	{ QQ_FIELD_BASE, 		QQ_FIELD_MULTI, 	"intro", 		N_("Personal Introduction"), NULL, 0 },
+	{ QQ_FIELD_ADDR, 		QQ_FIELD_STRING, "city",			N_("City/Area"), NULL, 0 },
+	{ QQ_FIELD_UNUSED, 	QQ_FIELD_STRING, "unknow3",	"Unknow3", NULL, 0 },
+	{ QQ_FIELD_UNUSED, 	QQ_FIELD_STRING, "unknow4",	"Unknow4", NULL, 0 },
+	{ QQ_FIELD_UNUSED, 	QQ_FIELD_STRING, "unknow5",	"Unknow5", NULL, 0 },
+	{ QQ_FIELD_UNUSED, 	QQ_FIELD_CHOICE, "pub_mobile",	N_("Publish Mobile"), publish_types, QQ_PUBLISH_SIZE },
+	{ QQ_FIELD_CONTACT, QQ_FIELD_CHOICE, "pub_contact",	N_("Publish Contact"), publish_types, QQ_PUBLISH_SIZE },
+	{ QQ_FIELD_EXT, 		QQ_FIELD_STRING, "college",			N_("College"), NULL, 0 },
+	{ QQ_FIELD_EXT, 		QQ_FIELD_CHOICE, "horoscope",	N_("Horoscope"), horoscope_names, QQ_HOROSCOPE_SIZE },
+	{ QQ_FIELD_EXT, 		QQ_FIELD_CHOICE, "zodiac",		N_("Zodiac"), zodiac_names, QQ_ZODIAC_SIZE },
+	{ QQ_FIELD_EXT, 		QQ_FIELD_CHOICE, "blood",			N_("Blood"), blood_types, QQ_BLOOD_SIZE },
+	{ QQ_FIELD_UNUSED, 	QQ_FIELD_STRING, "qq_show",	"QQ Show", NULL, 0 },
+	{ QQ_FIELD_UNUSED, 	QQ_FIELD_STRING, "unknow6",	"Unknow6", NULL, 0 },
+	{ QQ_FIELD_UNUSED, 	QQ_FIELD_STRING, "LAST_2005",	"LAST_2005", NULL, 0 }
+};
+
+typedef struct _modify_info_request {
+	PurpleConnection *gc;
+	int iclass;
+	gchar **segments;
+} modify_info_request;
 
-/* We get an info packet on ourselves before we modify our information.
- * Even though not all of the information is modifiable, it still
- * all needs to be there when we send out the modify info packet */
-typedef struct _modify_info_data {
-	PurpleConnection *gc;
-	contact_info *info;
-} modify_info_data;
-
-/* return -1 as a sentinel */
-static gint choice_index(const gchar *value, const gchar **choice, gint choice_size)
+#ifdef DEBUG
+static void info_debug(gchar **segments)
 {
-	gint len, i;
-
-	len = strlen(value);
-	if (len > 3 || len == 0) return -1;
-	for (i = 0; i < len; i++) {
-		if (!g_ascii_isdigit(value[i]))
-			return -1;
+#if 0
+	int index;
+	gchar *utf8_str;
+	for (index = 0; segments[index] != NULL && index < QQ_INFO_LAST; index++) {
+		if (field_infos[index].type == QQ_FIELD_STRING
+				|| field_infos[index].type == QQ_FIELD_LABEL
+				|| field_infos[index].type == QQ_FIELD_MULTI
+				|| index == QQ_INFO_GENDER)  {
+			utf8_str = qq_to_utf8(segments[index], QQ_CHARSET_DEFAULT);
+			purple_debug_info("QQ_BUDDY_INFO", "%s: %s\n", field_infos[index].text, utf8_str);
+			g_free(utf8_str);
+			continue;
+		}
+		purple_debug_info("QQ_BUDDY_INFO", "%s: %s\n", field_infos[index].text, segments[index]);
 	}
-	i = strtol(value, NULL, 10);
-	if (i >= choice_size)
-		return -1;
-
-	return i;
+#endif
 }
+#endif
 
-/* return should be freed */
-static gchar *field_value(const gchar *field, const gchar **choice, gint choice_size)
+static void info_display_only(PurpleConnection *gc, gchar **segments)
 {
-	gint index, len;
+	PurpleNotifyUserInfo *user_info;
+	gchar *utf8_value;
+	int index;
+	int choice_num;
+
+	user_info = purple_notify_user_info_new();
 
-	len = strlen(field);
-	if (len == 0) {
-		return NULL;
-	} else if (choice != NULL) {
-		/* some choice fields are also customizable */
-		index = choice_index(field, choice, choice_size);
-		if (index == -1) {
-			if (strcmp(field, "-") != 0) {
-				return qq_to_utf8(field, QQ_CHARSET_DEFAULT);
-			} else {
-				return NULL;
-			}
-			/* else ASCIIized index */
-		} else {
-			if (strcmp(choice[index], "-") != 0)
-				return g_strdup(choice[index]);
-			else
-				return NULL;
+	for (index = 1; segments[index] != NULL && index < QQ_INFO_LAST; index++) {
+		if (field_infos[index].iclass == QQ_FIELD_UNUSED) {
+			continue;
 		}
-	} else {
-		if (strcmp(field, "-") != 0) {
-			return qq_to_utf8(field, QQ_CHARSET_DEFAULT);
-		} else {
-			return NULL;
+		switch (field_infos[index].type) {
+			case QQ_FIELD_BOOL:
+				purple_notify_user_info_add_pair(user_info, field_infos[index].text,
+					strtol(segments[index], NULL, 10) ? _("True") : _("False"));
+				break;
+			case QQ_FIELD_CHOICE:
+				choice_num = strtol(segments[index], NULL, 10);
+				if (choice_num < 0 || choice_num >= field_infos[index].choice_size)	choice_num = 0;
+
+				purple_notify_user_info_add_pair(user_info, field_infos[index].text, field_infos[index].choice[choice_num]);
+				break;
+			case QQ_FIELD_LABEL:
+			case QQ_FIELD_STRING:
+			case QQ_FIELD_MULTI:
+			default:
+				if (strlen(segments[index]) != 0) {
+					utf8_value = qq_to_utf8(segments[index], QQ_CHARSET_DEFAULT);
+					purple_notify_user_info_add_pair(user_info, field_infos[index].text, utf8_value);
+					g_free(utf8_value);
+				}
+				break;
 		}
 	}
-}
 
-static gboolean append_field_value(PurpleNotifyUserInfo *user_info, const gchar *field,
-		const gchar *title, const gchar **choice, gint choice_size)
-{
-	gchar *value = field_value(field, choice, choice_size);
-
-	if (value != NULL) {
-		purple_notify_user_info_add_pair(user_info, title, value);
-		g_free(value);
-
-		return TRUE;
-	}
-
-	return FALSE;
-}
-
-static PurpleNotifyUserInfo *
-	info_to_notify_user_info(const contact_info *info)
-{
-	PurpleNotifyUserInfo *user_info = purple_notify_user_info_new();
-	const gchar *intro;
-	gboolean has_extra_info = FALSE;
-
-	purple_notify_user_info_add_pair(user_info, QQ_NUMBER, info->uid);
-
-	append_field_value(user_info, info->nick, QQ_NICKNAME, NULL, 0);
-	append_field_value(user_info, info->name, QQ_NAME, NULL, 0);
-	append_field_value(user_info, info->age, QQ_AGE, NULL, 0);
-	append_field_value(user_info, info->gender, QQ_GENDER, genders, QQ_GENDER_SIZE);
-	append_field_value(user_info, info->country, QQ_COUNTRY, NULL, 0);
-	append_field_value(user_info, info->province, QQ_PROVINCE, NULL, 0);
-	append_field_value(user_info, info->city, QQ_CITY, NULL, 0);
-
-	purple_notify_user_info_add_section_header(user_info, QQ_ADDITIONAL_INFORMATION);
-
-	has_extra_info |= append_field_value(user_info, info->horoscope, QQ_HOROSCOPE, horoscope_names, QQ_HOROSCOPE_SIZE);
-	has_extra_info |= append_field_value(user_info, info->occupation, QQ_OCCUPATION, NULL, 0);
-	has_extra_info |= append_field_value(user_info, info->zodiac, QQ_ZODIAC, zodiac_names, QQ_ZODIAC_SIZE);
-	has_extra_info |= append_field_value(user_info, info->blood, QQ_BLOOD, blood_types, QQ_BLOOD_SIZE);
-	has_extra_info |= append_field_value(user_info, info->college, QQ_COLLEGE, NULL, 0);
-	has_extra_info |= append_field_value(user_info, info->email, QQ_EMAIL, NULL, 0);
-	has_extra_info |= append_field_value(user_info, info->address, QQ_ADDRESS, NULL, 0);
-	has_extra_info |= append_field_value(user_info, info->zipcode, QQ_ZIPCODE, NULL, 0);
-	has_extra_info |= append_field_value(user_info, info->hp_num, QQ_CELL, NULL, 0);
-	has_extra_info |= append_field_value(user_info, info->tel, QQ_TELEPHONE, NULL, 0);
-	has_extra_info |= append_field_value(user_info, info->homepage, QQ_HOMEPAGE, NULL, 0);
+	purple_notify_userinfo(gc, segments[0], user_info, NULL, NULL);
 
-	if (!has_extra_info)
-		purple_notify_user_info_remove_last_item(user_info);
-
-	intro = field_value(info->intro, NULL, 0);
-	if (intro) {
-		purple_notify_user_info_add_pair(user_info, QQ_INTRO, intro);
-	}
-
-	/* for debugging */
-	/*
-	   g_string_append_printf(info_text, "<br /><br /><b>%s</b><br />", "Miscellaneous");
-	   append_field_value(info_text, info->pager_sn, "pager_sn", NULL, 0);
-	   append_field_value(info_text, info->pager_num, "pager_num", NULL, 0);
-	   append_field_value(info_text, info->pager_sp, "pager_sp", NULL, 0);
-	   append_field_value(info_text, info->pager_base_num, "pager_base_num", NULL, 0);
-	   append_field_value(info_text, info->pager_type, "pager_type", NULL, 0);
-	   append_field_value(info_text, info->auth_type, "auth_type", NULL, 0);
-	   append_field_value(info_text, info->unknown1, "unknown1", NULL, 0);
-	   append_field_value(info_text, info->unknown2, "unknown2", NULL, 0);
-	   append_field_value(info_text, info->face, "face", NULL, 0);
-	   append_field_value(info_text, info->hp_type, "hp_type", NULL, 0);
-	   append_field_value(info_text, info->unknown3, "unknown3", NULL, 0);
-	   append_field_value(info_text, info->unknown4, "unknown4", NULL, 0);
-	   append_field_value(info_text, info->unknown5, "unknown5", NULL, 0);
-	   append_field_value(info_text, info->is_open_hp, "is_open_hp", NULL, 0);
-	   append_field_value(info_text, info->is_open_contact, "is_open_contact", NULL, 0);
-	   append_field_value(info_text, info->qq_show, "qq_show", NULL, 0);
-	   append_field_value(info_text, info->unknown6, "unknown6", NULL, 0);
-	   */
-
-	return user_info;
-}
-
-/* send a packet to get detailed information of uid */
-void qq_send_packet_get_info(PurpleConnection *gc, guint32 uid, gboolean show_window)
-{
-	qq_data *qd;
-	gchar uid_str[11];
-	qq_info_query *query;
-
-	g_return_if_fail(uid != 0);
-
-	qd = (qq_data *) gc->proto_data;
-	g_snprintf(uid_str, sizeof(uid_str), "%d", uid);
-	qq_send_cmd(gc, QQ_CMD_GET_BUDDY_INFO, (guint8 *) uid_str, strlen(uid_str));
-
-	query = g_new0(qq_info_query, 1);
-	query->uid = uid;
-	query->show_window = show_window;
-	query->modify_info = FALSE;
-	qd->info_query = g_list_append(qd->info_query, query);
+	purple_notify_user_info_destroy(user_info);
+	g_strfreev(segments);
 }
 
 void qq_request_buddy_info(PurpleConnection *gc, guint32 uid,
-		gint update_class, guint32 ship32)
+		gint update_class, int action)
 {
 	qq_data *qd;
 	gchar raw_data[16] = {0};
@@ -304,418 +230,238 @@
 	qd = (qq_data *) gc->proto_data;
 	g_snprintf(raw_data, sizeof(raw_data), "%d", uid);
 	qq_send_cmd_mess(gc, QQ_CMD_GET_BUDDY_INFO, (guint8 *) raw_data, strlen(raw_data),
-			update_class, ship32);
-}
-
-/* set up the fields requesting personal information and send a get_info packet
- * for myself */
-void qq_prepare_modify_info(PurpleConnection *gc)
-{
-	qq_data *qd;
-	GList *ql;
-	qq_info_query *query;
-
-	qd = (qq_data *) gc->proto_data;
-	qq_send_packet_get_info(gc, qd->uid, FALSE);
-	/* traverse backwards so we get the most recent info_query */
-	for (ql = g_list_last(qd->info_query); ql != NULL; ql = g_list_previous(ql)) {
-		query = ql->data;
-		if (query->uid == qd->uid)
-			query->modify_info = TRUE;
-	}
+			update_class, action);
 }
 
 /* send packet to modify personal information */
-static void qq_send_packet_modify_info(PurpleConnection *gc, contact_info *info)
+static void request_change_info(PurpleConnection *gc, gchar **segments)
 {
 	gint bytes = 0;
 	guint8 raw_data[MAX_PACKET_SIZE - 128] = {0};
 	guint8 bar;
+	gchar *join;
 
-	g_return_if_fail(info != NULL);
+	g_return_if_fail(segments != NULL);
 
 	bar = 0x1f;
 
 	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_put8(raw_data + bytes, bar);
 
 	/* important! skip the first uid entry */
-	/*
-	   for (i = 1; i < QQ_CONTACT_FIELDS; i++) {
-	   create_packet_b(raw_data, &cursor, bar);
-	   create_packet_data(raw_data, &cursor, (guint8 *) segments[i], strlen(segments[i]));
-	   }
-	   */
-	/* uid */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->uid, strlen(info->uid));
-	/* nick */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->nick, strlen(info->nick));
-	/* country */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->country, strlen(info->country));
-	/* province */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->province, strlen(info->province));
-	/* zipcode */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->zipcode, strlen(info->zipcode));
-	/* address */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->address, strlen(info->address));
-	/* tel */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->tel, strlen(info->tel));
-	/* age */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->age, strlen(info->age));
-	/* gender */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->gender, strlen(info->gender));
-	/* name */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->name, strlen(info->name));
-	/* email */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->email, strlen(info->email));
-	/* pager_sn */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->pager_sn, strlen(info->pager_sn));
-	/* pager_num */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->pager_num, strlen(info->pager_num));
-	/* pager_sp */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->pager_sp, strlen(info->pager_sp));
-	/* pager_base_num */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->pager_base_num, strlen(info->pager_base_num));
-	/* pager_type */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->pager_type, strlen(info->pager_type));
-	/* occupation */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->occupation, strlen(info->occupation));
-	/* homepage */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->homepage, strlen(info->homepage));
-	/* auth_type */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->auth_type, strlen(info->auth_type));
-	/* unknown1 */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->unknown1, strlen(info->unknown1));
-	/* unknown2 */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->unknown2, strlen(info->unknown2));
-	/* face */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->face, strlen(info->face));
-	/* hp_num */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->hp_num, strlen(info->hp_num));
-	/* hp_type */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->hp_type, strlen(info->hp_type));
-	/* intro */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->intro, strlen(info->intro));
-	/* city */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->city, strlen(info->city));
-	/* unknown3 */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->unknown3, strlen(info->unknown3));
-	/* unknown4 */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->unknown4, strlen(info->unknown4));
-	/* unknown5 */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->unknown5, strlen(info->unknown5));
-	/* is_open_hp */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->is_open_hp, strlen(info->is_open_hp));
-	/* is_open_contact */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->is_open_contact, strlen(info->is_open_contact));
-	/* college */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->college, strlen(info->college));
-	/* horoscope */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->horoscope, strlen(info->horoscope));
-	/* zodiac */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->zodiac, strlen(info->zodiac));
-	/* blood */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->blood, strlen(info->blood));
-	/* qq_show */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->qq_show, strlen(info->qq_show));
-	/* unknown6 */
-	bytes += qq_put8(raw_data + bytes, bar);
-	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->unknown6, strlen(info->unknown6));
+	join = g_strjoinv("\x1f", segments + 1);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)join, strlen(join));
+	g_free(join);
 
 	bytes += qq_put8(raw_data + bytes, bar);
 
+	/* qq_show_packet("request_modify_info", raw_data, bytes); */
 	qq_send_cmd(gc, QQ_CMD_UPDATE_INFO, raw_data, bytes);
-
-}
-
-static void modify_info_cancel_cb(modify_info_data *mid)
-{
-	qq_data *qd;
-
-	qd = (qq_data *) mid->gc->proto_data;
-	qd->modifying_info = FALSE;
-
-	g_strfreev((gchar **) mid->info);
-	g_free(mid);
 }
 
-static gchar *parse_field(PurpleRequestField *field, gboolean choice)
+static void info_modify_cancel_cb(modify_info_request *info_request)
 {
-	gchar *value;
-
-	if (choice) {
-		value = g_strdup_printf("%d", purple_request_field_choice_get_value(field));
-	} else {
-		value = (gchar *) purple_request_field_string_get_value(field);
-		if (value == NULL)
-			value = g_strdup("-");
-		else
-			value = utf8_to_qq(value, QQ_CHARSET_DEFAULT);
-	}
-
-	return value;
+	g_strfreev(info_request->segments);
+	g_free(info_request);
 }
 
 /* parse fields and send info packet */
-static void modify_info_ok_cb(modify_info_data *mid, PurpleRequestFields *fields)
+static void info_modify_ok_cb(modify_info_request *info_request, PurpleRequestFields *fields)
 {
 	PurpleConnection *gc;
 	qq_data *qd;
-	GList *groups;
-	contact_info *info;
-
-	gc = mid->gc;
-	qd = (qq_data *) gc->proto_data;
-	qd->modifying_info = FALSE;
-
-	info = mid->info;
+	gchar **segments;
+	int index;
+	const char *utf8_str;
+	gchar *value;
+	int choice_num;
 
-	groups = purple_request_fields_get_groups(fields);
-	while (groups != NULL) {
-		PurpleRequestFieldGroup *group = groups->data;
-		const char *g_name = purple_request_field_group_get_title(group);
-		GList *fields = purple_request_field_group_get_fields(group);
-
-		if (g_name == NULL)
-			continue;
+	gc = info_request->gc;
+	g_return_if_fail(gc != NULL && info_request->gc);
+	qd = (qq_data *) gc->proto_data;
+	segments = info_request->segments;
+	g_return_if_fail(segments != NULL);
 
-		while (fields != NULL) {
-			PurpleRequestField *field = fields->data;
-			const char *f_id = purple_request_field_get_id(field);
-
-			if (!strcmp(QQ_PRIMARY_INFORMATION, g_name)) {
-
-				if (!strcmp(f_id, "uid"))
-					info->uid = parse_field(field, FALSE);
-				else if (!strcmp(f_id, "nick"))
-					info->nick = parse_field(field, FALSE);
-				else if (!strcmp(f_id, "name"))
-					info->name = parse_field(field, FALSE);
-				else if (!strcmp(f_id, "age"))
-					info->age = parse_field(field, FALSE);
-				else if (!strcmp(f_id, "gender"))
-					info->gender = parse_field(field, TRUE);
-				else if (!strcmp(f_id, "country"))
-					info->country = parse_field(field, FALSE);
-				else if (!strcmp(f_id, "province"))
-					info->province = parse_field(field, FALSE);
-				else if (!strcmp(f_id, "city"))
-					info->city = parse_field(field, FALSE);
-
-			} else if (!strcmp(QQ_ADDITIONAL_INFORMATION, g_name)) {
+	for (index = 1; segments[index] != NULL && index < QQ_INFO_LAST; index++) {
+		if (field_infos[index].iclass == QQ_FIELD_UNUSED) {
+			continue;
+		}
+		if (!purple_request_fields_exists(fields, field_infos[index].id)) {
+			continue;
+		}
+		switch (field_infos[index].type) {
+			case QQ_FIELD_BOOL:
+				value = purple_request_fields_get_bool(fields, field_infos[index].id)
+						? g_strdup("1") : g_strdup("0");
+				g_free(segments[index]);
+				segments[index] = value;
+				break;
+			case QQ_FIELD_CHOICE:
+				choice_num = purple_request_fields_get_choice(fields, field_infos[index].id);
+				if (choice_num < 0 || choice_num >= field_infos[index].choice_size)	choice_num = 0;
 
-				if (!strcmp(f_id, "horoscope"))
-					info->horoscope = parse_field(field, TRUE);
-				else if (!strcmp(f_id, "occupation"))
-					info->occupation = parse_field(field, FALSE);
-				else if (!strcmp(f_id, "zodiac"))
-					info->zodiac = parse_field(field, TRUE);
-				else if (!strcmp(f_id, "blood"))
-					info->blood = parse_field(field, TRUE);
-				else if (!strcmp(f_id, "college"))
-					info->college = parse_field(field, FALSE);
-				else if (!strcmp(f_id, "email"))
-					info->email = parse_field(field, FALSE);
-				else if (!strcmp(f_id, "address"))
-					info->address = parse_field(field, FALSE);
-				else if (!strcmp(f_id, "zipcode"))
-					info->zipcode = parse_field(field, FALSE);
-				else if (!strcmp(f_id, "hp_num"))
-					info->hp_num = parse_field(field, FALSE);
-				else if (!strcmp(f_id, "tel"))
-					info->tel = parse_field(field, FALSE);
-				else if (!strcmp(f_id, "homepage"))
-					info->homepage = parse_field(field, FALSE);
+				if (index == QQ_INFO_GENDER) {
+					/* QQ Server only recept gender in Chinese */
+					value = g_strdup(genders_zh[choice_num]);
+				} else {
+					value = g_strdup_printf("%d", choice_num);
+				}
+				g_free(segments[index]);
+				segments[index] = value;
+				break;
+			case QQ_FIELD_LABEL:
+			case QQ_FIELD_STRING:
+			case QQ_FIELD_MULTI:
+			default:
+				utf8_str = purple_request_fields_get_string(fields, field_infos[index].id);
+				if (utf8_str == NULL) {
+					value = g_strdup("-");
+				} else {
+					value = utf8_to_qq(utf8_str, QQ_CHARSET_DEFAULT);
+					if (value == NULL) value = g_strdup("-");
+				}
+				g_free(segments[index]);
+				segments[index] = value;
+				break;
+		}
+	}
+	request_change_info(gc, segments);
 
-			} else if (!strcmp(QQ_INTRO, g_name)) {
-
-				if (!strcmp(f_id, "intro"))
-					info->intro = parse_field(field, FALSE);
-
-			}
-
-			fields = fields->next;
-		}
-
-		groups = groups->next;
-	}
-
-	/* This casting looks like a horrible idea to me -DAA
-	 * yes, rewritten -s3e
-	 * qq_send_packet_modify_info(gc, (gchar **) info);
-	 */
-	qq_send_packet_modify_info(gc, info);
-
-	g_strfreev((gchar **) mid->info);
-	g_free(mid);
+	g_strfreev(segments);
+	g_free(info_request);
 }
 
-static PurpleRequestFieldGroup *setup_field_group(PurpleRequestFields *fields, const gchar *title)
-{
-	PurpleRequestFieldGroup *group;
-
-	group = purple_request_field_group_new(title);
-	purple_request_fields_add_group(fields, group);
-
-	return group;
-}
-
-static void add_string_field_to_group(PurpleRequestFieldGroup *group,
-		const gchar *id, const gchar *title, const gchar *value)
+static void field_request_new(PurpleRequestFieldGroup *group, gint index, gchar **segments)
 {
 	PurpleRequestField *field;
 	gchar *utf8_value;
+	int choice_num;
+	int i;
 
-	utf8_value = qq_to_utf8(value, QQ_CHARSET_DEFAULT);
-	field = purple_request_field_string_new(id, title, utf8_value, FALSE);
-	purple_request_field_group_add_field(group, field);
-	g_free(utf8_value);
+	g_return_if_fail(index >=0 && segments[index] != NULL && index < QQ_INFO_LAST);
+
+	switch (field_infos[index].type) {
+		case QQ_FIELD_STRING:
+		case QQ_FIELD_MULTI:
+			utf8_value = qq_to_utf8(segments[index], QQ_CHARSET_DEFAULT);
+			if (field_infos[index].type == QQ_FIELD_STRING) {
+				field = purple_request_field_string_new(
+						field_infos[index].id, field_infos[index].text, utf8_value, FALSE);
+			} else {
+				field = purple_request_field_string_new(
+						field_infos[index].id, field_infos[index].text, utf8_value, TRUE);
+			}
+			purple_request_field_group_add_field(group, field);
+			g_free(utf8_value);
+			break;
+		case QQ_FIELD_BOOL:
+			field = purple_request_field_bool_new(
+					field_infos[index].id, field_infos[index].text,
+					strtol(segments[index], NULL, 10) ? TRUE : FALSE);
+			purple_request_field_group_add_field(group, field);
+			break;
+		case QQ_FIELD_CHOICE:
+			choice_num = strtol(segments[index], NULL, 10);
+			if (choice_num < 0 || choice_num >= field_infos[index].choice_size)	choice_num = 0;
+
+			if (index == QQ_INFO_GENDER && strlen(segments[index]) != 0) {
+				for (i = 0; i < QQ_GENDER_SIZE; i++) {
+					if (strcmp(segments[index], genders_zh[i]) == 0) {
+						choice_num = i;
+					}
+				}
+			}
+			field = purple_request_field_choice_new(
+					field_infos[index].id, field_infos[index].text, choice_num);
+			for (i = 0; i < field_infos[index].choice_size; i++) {
+				purple_request_field_choice_add(field, field_infos[index].choice[i]);
+			}
+			purple_request_field_group_add_field(group, field);
+			break;
+		case QQ_FIELD_LABEL:
+		default:
+			field = purple_request_field_label_new(field_infos[index].id, segments[index]);
+			purple_request_field_group_add_field(group, field);
+			break;
+	}
 }
 
-static void add_choice_field_to_group(PurpleRequestFieldGroup *group,
-		const gchar *id, const gchar *title, const gchar *value,
-		const gchar **choice, gint choice_size)
-{
-	PurpleRequestField *field;
-	gint i, index;
-
-	index = choice_index(value, choice, choice_size);
-	field = purple_request_field_choice_new(id, title, index);
-	for (i = 0; i < choice_size; i++)
-		purple_request_field_choice_add(field, choice[i]);
-	purple_request_field_group_add_field(group, field);
-}
-
-/* take the info returned by a get_info packet for myself and set up a request form */
-static void create_modify_info_dialogue(PurpleConnection *gc, const contact_info *info)
+static void info_modify_dialogue(PurpleConnection *gc, gchar **segments, int iclass)
 {
 	qq_data *qd;
 	PurpleRequestFieldGroup *group;
 	PurpleRequestFields *fields;
-	PurpleRequestField *field;
-	modify_info_data *mid;
+	modify_info_request *info_request;
+	gchar *utf8_title, *utf8_prim;
+	int index;
 
-	/* so we only have one dialog open at a time */
 	qd = (qq_data *) gc->proto_data;
-	if (!qd->modifying_info) {
-		qd->modifying_info = TRUE;
-
-		fields = purple_request_fields_new();
+	/* Keep one dialog once a time */
+	purple_request_close_with_handle(gc);
 
-		group = setup_field_group(fields, QQ_PRIMARY_INFORMATION);
-		field = purple_request_field_string_new("uid", QQ_NUMBER, info->uid, FALSE);
-		purple_request_field_group_add_field(group, field);
-		purple_request_field_string_set_editable(field, FALSE);
-		add_string_field_to_group(group, "nick", QQ_NICKNAME, info->nick);
-		add_string_field_to_group(group, "name", QQ_NAME, info->name);
-		add_string_field_to_group(group, "age", QQ_AGE, info->age);
-		add_choice_field_to_group(group, "gender", QQ_GENDER, info->gender, genders, QQ_GENDER_SIZE);
-		add_string_field_to_group(group, "country", QQ_COUNTRY, info->country);
-		add_string_field_to_group(group, "province", QQ_PROVINCE, info->province);
-		add_string_field_to_group(group, "city", QQ_CITY, info->city);
+	fields = purple_request_fields_new();
+	group = purple_request_field_group_new(NULL);
+	purple_request_fields_add_group(fields, group);
 
-		group = setup_field_group(fields, QQ_ADDITIONAL_INFORMATION);
-		add_choice_field_to_group(group, "horoscope", QQ_HOROSCOPE, info->horoscope, horoscope_names, QQ_HOROSCOPE_SIZE);
-		add_string_field_to_group(group, "occupation", QQ_OCCUPATION, info->occupation);
-		add_choice_field_to_group(group, "zodiac", QQ_ZODIAC, info->zodiac, zodiac_names, QQ_ZODIAC_SIZE);
-		add_choice_field_to_group(group, "blood", QQ_BLOOD, info->blood, blood_types, QQ_BLOOD_SIZE);
-		add_string_field_to_group(group, "college", QQ_COLLEGE, info->college);
-		add_string_field_to_group(group, "email", QQ_EMAIL, info->email);
-		add_string_field_to_group(group, "address", QQ_ADDRESS, info->address);
-		add_string_field_to_group(group, "zipcode", QQ_ZIPCODE, info->zipcode);
-		add_string_field_to_group(group, "hp_num", QQ_CELL, info->hp_num);
-		add_string_field_to_group(group, "tel", QQ_TELEPHONE, info->tel);
-		add_string_field_to_group(group, "homepage", QQ_HOMEPAGE, info->homepage);
+	for (index = 1; segments[index] != NULL && index < QQ_INFO_LAST; index++) {
+		if (field_infos[index].iclass != iclass) {
+			continue;
+		}
+		field_request_new(group, index, segments);
+	}
 
-		group = setup_field_group(fields, QQ_INTRO);
-		field = purple_request_field_string_new("intro", QQ_INTRO, info->intro, TRUE);
-		purple_request_field_group_add_field(group, field);
+	switch (iclass) {
+		case QQ_FIELD_CONTACT:
+			utf8_title = g_strdup(_("Modify Contact"));
+			utf8_prim = g_strdup_printf("%s for %s", _("Modify Contact"), segments[0]);
+		case QQ_FIELD_ADDR:
+			utf8_title = g_strdup(_("Modify Address"));
+			utf8_prim = g_strdup_printf("%s for %s", _("Modify Address"), segments[0]);
+		case QQ_FIELD_EXT:
+			utf8_title = g_strdup(_("Modify Extend Information"));
+			utf8_prim = g_strdup_printf("%s for %s", _("Modify Extend Information"), segments[0]);
+			break;
+		case QQ_FIELD_BASE:
+		default:
+			utf8_title = g_strdup(_("Modify Information"));
+			utf8_prim = g_strdup_printf("%s for %s", _("Modify Information"), segments[0]);
+	}
 
-		/* prepare unmodifiable info */
-		mid = g_new0(modify_info_data, 1);
-		mid->gc = gc;
-		/* QQ_CONTACT_FIELDS+1 so that the array is NULL-terminated and can be g_strfreev()'ed later */
-		mid->info = (contact_info *) g_new0(gchar *, QQ_CONTACT_FIELDS+1);
-		mid->info->pager_sn = g_strdup(info->pager_sn);
-		mid->info->pager_num = g_strdup(info->pager_num);
-		mid->info->pager_sp = g_strdup(info->pager_sp);
-		mid->info->pager_base_num = g_strdup(info->pager_base_num);
-		mid->info->pager_type = g_strdup(info->pager_type);
-		mid->info->auth_type = g_strdup(info->auth_type);
-		mid->info->unknown1 = g_strdup(info->unknown1);
-		mid->info->unknown2 = g_strdup(info->unknown2);
-		mid->info->face = g_strdup(info->face);
-		mid->info->hp_type = g_strdup(info->hp_type);
-		mid->info->unknown3 = g_strdup(info->unknown3);
-		mid->info->unknown4 = g_strdup(info->unknown4);
-		mid->info->unknown5 = g_strdup(info->unknown5);
-		/* TODO stop hiding these 2 */
-		mid->info->is_open_hp = g_strdup(info->is_open_hp);
-		mid->info->is_open_contact = g_strdup(info->is_open_contact);
-		mid->info->qq_show = g_strdup(info->qq_show);
-		mid->info->unknown6 = g_strdup(info->unknown6);
+	info_request = g_new0(modify_info_request, 1);
+	info_request->gc = gc;
+	info_request->iclass = iclass;
+	info_request->segments = segments;
 
-		purple_request_fields(gc, _("Modify information"),
-				_("Modify information"), NULL, fields,
-				_("Update information"), G_CALLBACK(modify_info_ok_cb),
-				_("Cancel"), G_CALLBACK(modify_info_cancel_cb),
-				purple_connection_get_account(gc), NULL, NULL,
-				mid);
-	}
+	purple_request_fields(gc,
+			utf8_title,
+			utf8_prim,
+			NULL,
+			fields,
+			_("Update"), G_CALLBACK(info_modify_ok_cb),
+			_("Cancel"), G_CALLBACK(info_modify_cancel_cb),
+			purple_connection_get_account(gc), NULL, NULL,
+			info_request);
+
+	g_free(utf8_title);
+	g_free(utf8_prim);
 }
 
 /* process the reply of modify_info packet */
-void qq_process_modify_info_reply(guint8 *data, gint data_len, PurpleConnection *gc)
+void qq_process_change_info(PurpleConnection *gc, guint8 *data, gint data_len)
 {
 	qq_data *qd;
-
 	g_return_if_fail(data != NULL && data_len != 0);
 
 	qd = (qq_data *) gc->proto_data;
 
 	data[data_len] = '\0';
-	if (qd->uid == atoi((gchar *) data)) {	/* return should be my uid */
-		purple_debug_info("QQ", "Update info ACK OK\n");
-		purple_notify_info(gc, _("QQ Buddy"), _("Successed:"), _("Change buddy information."));
+	if (qd->uid != atoi((gchar *) data)) {	/* return should be my uid */
+		purple_debug_info("QQ", "Failed Updating info\n");
+		qq_got_attention(gc, _("Failed changing buddy information."));
 	}
 }
 
-static void _qq_send_packet_modify_face(PurpleConnection *gc, gint face_num)
+static void request_set_buddy_icon(PurpleConnection *gc, gint face_num)
 {
 	PurpleAccount *account = purple_connection_get_account(gc);
 	PurplePresence *presence = purple_account_get_presence(account);
@@ -732,204 +478,276 @@
 	}
 
 	qd->my_icon = 3 * (face_num - 1) + offset;
-	qd->modifying_face = TRUE;
-	qq_send_packet_get_info(gc, qd->uid, FALSE);
-}
-
-void qq_set_buddy_icon_for_user(PurpleAccount *account, const gchar *who, const gchar *icon_num, const gchar *iconfile)
-{
-	gchar *data;
-	gsize len;
-
-	if (!g_file_get_contents(iconfile, &data, &len, NULL)) {
-		g_return_if_reached();
-	} else {
-		purple_buddy_icons_set_for_user(account, who, data, len, icon_num);
-	}
-}
-
-/* TODO: custom faces for QQ members and users with level >= 16 */
-void qq_set_my_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img)
-{
-	gchar *icon;
-	gint icon_num;
-	gint icon_len;
-	PurpleAccount *account = purple_connection_get_account(gc);
-	const gchar *icon_path = purple_account_get_buddy_icon_path(account);
-	const gchar *buddy_icon_dir = qq_buddy_icon_dir();
-	gint prefix_len = strlen(QQ_ICON_PREFIX);
-	gint suffix_len = strlen(QQ_ICON_SUFFIX);
-	gint dir_len = buddy_icon_dir ? strlen(buddy_icon_dir) : 0;
-	gchar *errmsg = g_strdup_printf(_("Setting custom faces is not currently supported. Please choose an image from %s."), buddy_icon_dir ? buddy_icon_dir : "(null)");
-	gboolean icon_global = purple_account_get_bool(gc->account, "use-global-buddyicon", TRUE);
-
-	if (!icon_path)
-		icon_path = "";
-
-	icon_len = strlen(icon_path) - dir_len - 1 - prefix_len - suffix_len;
-
-	/* make sure we're using an appropriate icon */
-	if (buddy_icon_dir && !(g_ascii_strncasecmp(icon_path, buddy_icon_dir, dir_len) == 0
-				&& icon_path[dir_len] == G_DIR_SEPARATOR
-				&& g_ascii_strncasecmp(icon_path + dir_len + 1, QQ_ICON_PREFIX, prefix_len) == 0
-				&& g_ascii_strncasecmp(icon_path + dir_len + 1 + prefix_len + icon_len, QQ_ICON_SUFFIX, suffix_len) == 0
-				&& icon_len <= 3)) {
-		if (icon_global)
-			purple_debug_error("QQ", "%s\n", errmsg);
-		else
-			purple_notify_error(gc, _("Invalid QQ Face"), errmsg, NULL);
-		g_free(errmsg);
-		return;
-	}
-	/* strip everything but number */
-	icon = g_strndup(icon_path + dir_len + 1 + prefix_len, icon_len);
-	icon_num = strtol(icon, NULL, 10);
-	g_free(icon);
-	/* ensure face number in proper range */
-	if (icon_num > QQ_FACES) {
-		if (icon_global)
-			purple_debug_error("QQ", "%s\n", errmsg);
-		else
-			purple_notify_error(gc, _("Invalid QQ Face"), errmsg, NULL);
-		g_free(errmsg);
-		return;
-	}
-	g_free(errmsg);
-	/* tell server my icon changed */
-	_qq_send_packet_modify_face(gc, icon_num);
-	/* display in blist */
-	qq_set_buddy_icon_for_user(account, account->username, icon, icon_path);
+	qq_request_buddy_info(gc, qd->uid, 0, QQ_BUDDY_INFO_SET_ICON);
 }
 
-
-static void _qq_update_buddy_icon(PurpleAccount *account, const gchar *name, gint face)
+void qq_change_icon_cb(PurpleConnection *gc, const char *filepath)
 {
-	PurpleBuddy *buddy;
-	gchar *icon_num_str = face_to_icon_str(face);
-	const gchar *old_icon_num = NULL;
+	gchar **segments;
+	const gchar *filename;
+	gint index;
+	gint face;
+	gchar *error;
+
+	g_return_if_fail(filepath != NULL);
 
-	if ((buddy = purple_find_buddy(account, name)))
-		old_icon_num = purple_buddy_icons_get_checksum_for_user(buddy);
+	purple_debug_info("QQ", "Change my icon to %s\n", filepath);
+	segments = g_strsplit_set(filepath, G_DIR_SEPARATOR_S, 0);
+
+#if 0
+	for (index = 0; segments[index] != NULL; index++) {
+		purple_debug_info("QQ", "Split to %s\n", segments[index]);
+	}
+#endif
 
-	if ((old_icon_num == NULL ||
-			strcmp(icon_num_str, old_icon_num)) && (qq_buddy_icon_dir() != NULL))
-	{
-		gchar *icon_path;
+	index = g_strv_length(segments) - 1;
+	if (index < 0) {
+		g_strfreev(segments);
+		return;
+	}
 
-		icon_path = g_strconcat(qq_buddy_icon_dir(), G_DIR_SEPARATOR_S,
-				QQ_ICON_PREFIX, icon_num_str,
-				QQ_ICON_SUFFIX, NULL);
+	filename = segments[index];
+	index = strcspn (filename, "0123456789");
+	if (index < 0 || index >= strlen(filename)) {
+		error = g_strdup_printf(_("Can not get face number in file name (%s)"), filename);
+		purple_notify_error(gc, _("QQ Buddy"), _("Failed change icon"), error);
+		g_free(error);
+		return;
+	}
+	face = strtol(filename+index, NULL, 10);
+	purple_debug_info("QQ", "Set face to %d\n", face);
 
-		qq_set_buddy_icon_for_user(account, name, icon_num_str, icon_path);
-		g_free(icon_path);
-	}
-	g_free(icon_num_str);
+	request_set_buddy_icon(gc, face);
+
+	g_strfreev(segments);
 }
 
-/* after getting info or modify myself, refresh the buddy list accordingly */
-static void qq_refresh_buddy_and_myself(contact_info *info, PurpleConnection *gc)
+void qq_set_custom_icon(PurpleConnection *gc, PurpleStoredImage *img)
 {
-	PurpleBuddy *b;
-	qq_data *qd;
-	qq_buddy *q_bud;
-	gchar *alias_utf8;
-	gchar *purple_name;
 	PurpleAccount *account = purple_connection_get_account(gc);
-
-	qd = (qq_data *) gc->proto_data;
-	purple_name = uid_to_purple_name(strtol(info->uid, NULL, 10));
-
-	alias_utf8 = qq_to_utf8(info->nick, QQ_CHARSET_DEFAULT);
-	if (qd->uid == strtol(info->uid, NULL, 10)) {	/* it is me */
-		qd->my_icon = strtol(info->face, NULL, 10);
-		if (alias_utf8 != NULL)
-			purple_account_set_alias(account, alias_utf8);
-	}
-	/* update buddy list (including myself, if myself is the buddy) */
-	b = purple_find_buddy(gc->account, purple_name);
-	q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
-	if (q_bud != NULL) {	/* I have this buddy */
-		q_bud->age = strtol(info->age, NULL, 10);
-		q_bud->gender = strtol(info->gender, NULL, 10);
-		q_bud->face = strtol(info->face, NULL, 10);
-		if (alias_utf8 != NULL)
-			q_bud->nickname = g_strdup(alias_utf8);
-		qq_update_buddy_contact(gc, q_bud);
-		_qq_update_buddy_icon(gc->account, purple_name, q_bud->face);
-	}
-	g_free(purple_name);
-	g_free(alias_utf8);
-}
-
-/* process reply to get_info packet */
-void qq_process_get_buddy_info(guint8 *data, gint data_len, PurpleConnection *gc)
-{
+	const gchar *icon_path = purple_account_get_buddy_icon_path(account);
 	gchar **segments;
-	qq_info_query *query;
-	qq_data *qd;
-	contact_info *info;
-	GList *list, *query_list;
-	PurpleNotifyUserInfo *user_info;
+	gint index;
 
-	g_return_if_fail(data != NULL && data_len != 0);
-
-	qd = (qq_data *) gc->proto_data;
-	list = query_list = NULL;
-	info = NULL;
-
-	if (NULL == (segments = split_data(data, data_len, "\x1e", QQ_CONTACT_FIELDS)))
-		return;
+	g_return_if_fail(icon_path != NULL);
 
-	info = (contact_info *) segments;
-	if (qd->modifying_face && strtol(info->face, NULL, 10) != qd->my_icon) {
-		gchar *icon = g_strdup_printf("%d", qd->my_icon);
-		qd->modifying_face = FALSE;
-		g_free(info->face);
-		info->face = icon;
-		qq_send_packet_modify_info(gc, (contact_info *)segments);
-	}
-
-	qq_refresh_buddy_and_myself(info, gc);
-
-	query_list = qd->info_query;
-	/* ensure we're processing the right query */
-	while (query_list) {
-		query = (qq_info_query *) query_list->data;
-		if (query->uid == atoi(info->uid)) {
-			if (query->show_window) {
-				user_info = info_to_notify_user_info(info);
-				purple_notify_userinfo(gc, info->uid, user_info, NULL, NULL);
-				purple_notify_user_info_destroy(user_info);
-			} else if (query->modify_info) {
-				create_modify_info_dialogue(gc, info);
-			}
-			qd->info_query = g_list_remove(qd->info_query, qd->info_query->data);
-			g_free(query);
-			break;
-		}
-		query_list = query_list->next;
+	/* Fixme:
+	 *  icon_path is always null
+	 *  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);
 }
 
-void qq_info_query_free(qq_data *qd)
+gchar *qq_get_icon_name(gint face)
+{
+	gint icon;
+	gchar *icon_name;
+
+	icon = face / 3 + 1;
+	if (icon < 1 || icon > QQ_FACES) {
+		icon = 1;
+	}
+
+	icon_name = g_strdup_printf("%s%d%s", QQ_ICON_PREFIX, icon, QQ_ICON_SUFFIX);
+	return icon_name;
+}
+
+gchar *qq_get_icon_path(gchar *icon_name)
+{
+	gchar *icon_path;
+	const gchar *icon_dir;
+#ifdef _WIN32
+	static char *dir = NULL;
+	if (dir == NULL) {
+		dir = g_build_filename(wpurple_install_dir(), "pixmaps",
+				"purple", "buddy_icons", "qq", NULL);
+	}
+#endif
+
+	icon_dir = purple_prefs_get_string("/plugins/prpl/qq/icon_dir");
+	if ( icon_dir == NULL || strlen(icon_dir) == 0) {
+#ifdef _WIN32
+			icon_dir = dir;
+#else
+			icon_dir = QQ_BUDDY_ICON_DIR;
+#endif
+	}
+	icon_path = g_strdup_printf("%s%c%s", icon_dir, G_DIR_SEPARATOR, icon_name);
+
+	return icon_path;
+}
+
+static void update_buddy_icon(PurpleAccount *account, const gchar *who, gint face)
 {
-	gint count;
-	qq_info_query *p;
+	PurpleBuddy *buddy;
+	const gchar *icon_name_prev = NULL;
+	gchar *icon_name;
+	gchar *icon_path;
+	gchar *icon_file_content;
+	gsize icon_file_size;
+
+	g_return_if_fail(account != NULL && who != NULL);
+
+	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);
+
+	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);
+		}
+	}
+	if (icon_name_prev != NULL && !strcmp(icon_name, icon_name_prev)) {
+		purple_debug_info("QQ", "Icon is not changed\n");
+		g_free(icon_name);
+		return;
+	}
 
-	g_return_if_fail(qd != NULL);
+	icon_path = qq_get_icon_path(icon_name);
+	if (icon_path == NULL) {
+		g_free(icon_name);
+		return;
+	}
+
+	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_buddy_icons_set_for_user(account, who,
+				icon_file_content, icon_file_size, icon_name);
+	}
+	g_free(icon_name);
+	g_free(icon_path);
+}
+
+/* after getting info or modify myself, refresh the buddy list accordingly */
+static void update_buddy_info(PurpleConnection *gc, gchar **segments)
+{
+	PurpleBuddy *buddy;
+	qq_data *qd;
+	qq_buddy_data *bd;
+	guint32 uid;
+	gchar *who;
+	gchar *alias_utf8;
+	PurpleAccount *account = purple_connection_get_account(gc);
+
+	qd = (qq_data *) gc->proto_data;
+
+	uid = strtol(segments[QQ_INFO_UID], NULL, 10);
+	who = uid_to_purple_name(uid);
 
-	count = 0;
-	while (qd->info_query != NULL) {
-		p = (qq_info_query *) (qd->info_query->data);
-		qd->info_query = g_list_remove(qd->info_query, p);
-		g_free(p);
-		count++;
+	qq_filter_str(segments[QQ_INFO_NICK]);
+	alias_utf8 = qq_to_utf8(segments[QQ_INFO_NICK], QQ_CHARSET_DEFAULT);
+	if (uid == qd->uid) {	/* it is me */
+		purple_debug_info("QQ", "Got my info\n");
+		qd->my_icon = strtol(segments[QQ_INFO_FACE], NULL, 10);
+		if (alias_utf8 != NULL) {
+			purple_account_set_alias(account, alias_utf8);
+		}
+		/* add me to buddy list */
+		buddy = qq_buddy_find_or_new(gc, uid);
+	} else {
+		buddy = purple_find_buddy(gc->account, who);
+	}
+
+	if (buddy == NULL || buddy->proto_data == NULL) {
+		g_free(who);
+		g_free(alias_utf8);
+		return;
+	}
+
+	/* update buddy list (including myself, if myself is the buddy) */
+	bd = (qq_buddy_data *)buddy->proto_data;
+
+	bd->age = strtol(segments[QQ_INFO_AGE], NULL, 10);
+	bd->gender = strtol(segments[QQ_INFO_GENDER], NULL, 10);
+	bd->face = strtol(segments[QQ_INFO_FACE], NULL, 10);
+	if (alias_utf8 != NULL) {
+		if (bd->nickname) g_free(bd->nickname);
+		bd->nickname = g_strdup(alias_utf8);
 	}
-	if (count > 0) {
-		purple_debug_info("QQ", "%d info queries are freed!\n", count);
+	bd->last_update = time(NULL);
+
+	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);
+
+	g_free(who);
+	g_free(alias_utf8);
+}
+
+/* process reply to get_info packet */
+void qq_process_get_buddy_info(guint8 *data, gint data_len, guint32 action, PurpleConnection *gc)
+{
+	qq_data *qd;
+	gchar **segments;
+	gint field_count;
+	gchar *icon_name;
+
+	g_return_if_fail(data != NULL && data_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+
+	if (qd->client_version >= 2008) {
+		field_count = QQ_INFO_LAST;
+	} else {
+		field_count = QQ_INFO_LAST_2007;
 	}
+	if (NULL == (segments = split_data(data, data_len, "\x1e", field_count)))
+		return;
+
+#ifdef DEBUG
+	info_debug(segments);
+#endif
+
+	if (action == QQ_BUDDY_INFO_SET_ICON) {
+		if (strtol(segments[QQ_INFO_FACE], NULL, 10) != qd->my_icon) {
+			icon_name = g_strdup_printf("%d", qd->my_icon);
+			g_free(segments[QQ_INFO_FACE]);
+			segments[QQ_INFO_FACE] = icon_name;
+
+			/* Update me in buddy list */
+			update_buddy_info(gc, segments);
+			/* send new face to server */
+			request_change_info(gc, segments);
+		}
+		g_strfreev(segments);
+		return;
+	}
+
+	update_buddy_info(gc, segments);
+	switch (action) {
+		case QQ_BUDDY_INFO_DISPLAY:
+			info_display_only(gc, segments);
+			break;
+		case QQ_BUDDY_INFO_SET_ICON:
+			g_return_if_reached();
+			break;
+		case QQ_BUDDY_INFO_MODIFY_BASE:
+			info_modify_dialogue(gc, segments, QQ_FIELD_BASE);
+			break;
+		case QQ_BUDDY_INFO_MODIFY_EXT:
+			info_modify_dialogue(gc, segments, QQ_FIELD_EXT);
+			break;
+		case QQ_BUDDY_INFO_MODIFY_ADDR:
+			info_modify_dialogue(gc, segments, QQ_FIELD_ADDR);
+			break;
+		case QQ_BUDDY_INFO_MODIFY_CONTACT:
+			info_modify_dialogue(gc, segments, QQ_FIELD_CONTACT);
+			break;
+		default:
+			g_strfreev(segments);
+			break;
+	}
+	return;
 }
 
 void qq_request_get_level(PurpleConnection *gc, guint32 uid)
@@ -938,101 +756,150 @@
 	guint8 buf[16] = {0};
 	gint bytes = 0;
 
-	bytes += qq_put8(buf + bytes, 0x00);
+	if (qd->client_version >= 2007) {
+		bytes += qq_put8(buf + bytes, 0x02);
+	} else {
+		bytes += qq_put8(buf + bytes, 0x00);
+	}
 	bytes += qq_put32(buf + bytes, uid);
+	qq_send_cmd(gc, QQ_CMD_GET_LEVEL, buf, bytes);
+}
 
-	qd = (qq_data *) gc->proto_data;
+void qq_request_get_level_2007(PurpleConnection *gc, guint32 uid)
+{
+	guint8 buf[16] = {0};
+	gint bytes = 0;
+
+	bytes += qq_put8(buf + bytes, 0x08);
+	bytes += qq_put32(buf + bytes, uid);
+	bytes += qq_put8(buf + bytes, 0x00);
 	qq_send_cmd(gc, QQ_CMD_GET_LEVEL, buf, bytes);
 }
 
 void qq_request_get_buddies_level(PurpleConnection *gc, gint update_class)
 {
+	qq_data *qd = (qq_data *) gc->proto_data;
+	PurpleBuddy *buddy;
+	qq_buddy_data *bd;
 	guint8 *buf;
-	guint16 size;
-	qq_buddy *q_bud;
-	qq_data *qd = (qq_data *) gc->proto_data;
-	GList *node = qd->buddies;
-	gint bytes = 0;
+	GSList *buddies, *it;
+	gint bytes;
 
-	if ( qd->buddies == NULL) {
-		return;
-	}
 	/* server only reply levels for online buddies */
-	size = 4 * g_list_length(qd->buddies) + 1 + 4;
-	buf = g_newa(guint8, size);
-	bytes += qq_put8(buf + bytes, 0x00);
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
 
-	while (NULL != node) {
-		q_bud = (qq_buddy *) node->data;
-		if (NULL != q_bud) {
-			bytes += qq_put32(buf + bytes, q_bud->uid);
-		}
-		node = node->next;
+	bytes = 0;
+	bytes += qq_put8(buf + bytes, 0x00);
+	buddies = purple_find_buddies(purple_connection_get_account(gc), NULL);
+	for (it = buddies; it; it = it->next) {
+		buddy = it->data;
+		if (buddy == NULL) continue;
+		if (buddy->proto_data == NULL) continue;
+		bd = (qq_buddy_data *)buddy->proto_data;
+		if (bd->uid == 0) continue;	/* keep me as end of packet*/
+		if (bd->uid == qd->uid) continue;
+		bytes += qq_put32(buf + bytes, bd->uid);
 	}
-
-	/* my id should be the end if included */
 	bytes += qq_put32(buf + bytes, qd->uid);
-	qq_send_cmd_mess(gc, QQ_CMD_GET_LEVEL, buf, size, update_class, 0);
+	qq_send_cmd_mess(gc, QQ_CMD_GET_LEVEL, buf, bytes, update_class, 0);
 }
 
-void qq_process_get_level_reply(guint8 *decr_buf, gint decr_len, PurpleConnection *gc)
+static void process_level(PurpleConnection *gc, guint8 *data, gint data_len)
 {
+	gint bytes = 0;
 	guint32 uid, onlineTime;
 	guint16 level, timeRemainder;
-	gchar *purple_name;
-	PurpleBuddy *b;
-	qq_buddy *q_bud;
-	gint i;
-	PurpleAccount *account = purple_connection_get_account(gc);
-	qq_data *qd = (qq_data *) gc->proto_data;
-	gint bytes = 0;
+	qq_buddy_data *bd;
 
-	decr_len--;
-	if (decr_len % 12 != 0) {
-		purple_debug_error("QQ",
-				"Get levels list of abnormal length. Truncating last %d bytes.\n", decr_len % 12);
-		decr_len -= (decr_len % 12);
-	}
-
-	bytes += 1;
-	/* this byte seems random */
-	/*
-	   purple_debug_info("QQ", "Byte one of get_level packet: %d\n", buf[0]);
-	   */
-	for (i = 0; i < decr_len; i += 12) {
-		bytes += qq_get32(&uid, decr_buf + bytes);
-		bytes += qq_get32(&onlineTime, decr_buf + bytes);
-		bytes += qq_get16(&level, decr_buf + bytes);
-		bytes += qq_get16(&timeRemainder, decr_buf + bytes);
+	while (data_len - bytes >= 12) {
+		bytes += qq_get32(&uid, data + bytes);
+		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);
-		if (uid == qd->uid) {
-			qd->my_level = level;
-			purple_debug_warning("QQ", "Got my levels as %d\n", qd->my_level);
-			continue;
-		}
 
-		purple_name = uid_to_purple_name(uid);
-		if (purple_name == NULL) {
-			continue;
-		}
-
-		b = purple_find_buddy(account, purple_name);
-		g_free(purple_name);
-
-		q_bud = NULL;
-		if (b != NULL) {
-			q_bud = (qq_buddy *) b->proto_data;
-		}
-
-		if (q_bud == NULL) {
+		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);
 			continue;
 		}
 
-		q_bud->onlineTime = onlineTime;
-		q_bud->level = level;
-		q_bud->timeRemainder = timeRemainder;
+		bd->onlineTime = onlineTime;
+		bd->level = level;
+		bd->timeRemainder = timeRemainder;
+	}
+
+	if (bytes != data_len) {
+		purple_debug_error("QQ",
+				"Wrong format of Get levels. Truncate %d bytes.\n", data_len - bytes);
 	}
 }
 
+static void process_level_2007(PurpleConnection *gc, guint8 *data, gint data_len)
+{
+	gint bytes;
+	guint32 uid, onlineTime;
+	guint16 level, timeRemainder;
+	qq_buddy_data *bd;
+	guint16 str_len;
+	gchar *str;
+	gchar *str_utf8;
+
+	bytes = 0;
+	bytes += qq_get32(&uid, data + bytes);
+	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);
+
+	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);
+		return;
+	}
+
+	bd->onlineTime = onlineTime;
+	bd->level = level;
+	bd->timeRemainder = timeRemainder;
+
+	/* extend bytes in qq2007*/
+	bytes += 4;	/* skip 8 bytes */
+	/* qq_show_packet("Buddies level", data + bytes, data_len - bytes); */
+
+	do {
+		bytes += qq_get16(&str_len, data + bytes);
+		if (str_len <= 0 || bytes + str_len > data_len) {
+			purple_debug_error("QQ",
+					"Wrong format of Get levels. Truncate %d bytes.\n", data_len - bytes);
+			break;
+		}
+		str = g_strndup((gchar *)data + bytes, str_len);
+		bytes += str_len;
+		str_utf8 = qq_to_utf8(str, QQ_CHARSET_DEFAULT);
+		purple_debug_info("QQ", "%s\n", str_utf8);
+		g_free(str_utf8);
+		g_free(str);
+	} while (bytes < data_len);
+}
+
+void qq_process_get_level_reply(guint8 *data, gint data_len, PurpleConnection *gc)
+{
+	gint bytes;
+	guint8 sub_cmd;
+
+	bytes = 0;
+	bytes += qq_get8(&sub_cmd, data + bytes);
+	switch (sub_cmd) {
+		case 0x08:
+			process_level_2007(gc, data + bytes, data_len - bytes);
+			break;
+		case 0x00:
+		case 0x02:
+		default:
+			process_level(gc, data + bytes, data_len - bytes);
+			break;
+	}
+}
+
--- a/libpurple/protocols/qq/buddy_info.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/buddy_info.h	Thu Nov 06 03:20:05 2008 +0000
@@ -64,25 +64,28 @@
 #define QQ_BUDDY_GENDER_MM          0x01
 #define QQ_BUDDY_GENDER_UNKNOWN     0xff
 
-#define QQ_ICON_PREFIX "qq_"
-#define QQ_ICON_SUFFIX ".png"
-
 enum {
 	QQ_BUDDY_INFO_UPDATE_ONLY = 0,
 	QQ_BUDDY_INFO_DISPLAY,
-	QQ_BUDDY_INFO_MODIFY,
+	QQ_BUDDY_INFO_SET_ICON,
+	QQ_BUDDY_INFO_MODIFY_BASE,
+	QQ_BUDDY_INFO_MODIFY_EXT,
+	QQ_BUDDY_INFO_MODIFY_ADDR,
+	QQ_BUDDY_INFO_MODIFY_CONTACT,
 };
 
-void qq_send_packet_get_info(PurpleConnection *gc, guint32 uid, gboolean show_window);
+gchar *qq_get_icon_name(gint face);
+gchar *qq_get_icon_path(gchar *icon_name);
+void qq_change_icon_cb(PurpleConnection *gc, const char *filepath);
+
 void qq_request_buddy_info(PurpleConnection *gc, guint32 uid,
-		gint update_class, guint32 ship32);
-void qq_set_my_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img);
-void qq_set_buddy_icon_for_user(PurpleAccount *account, const gchar *who, const gchar *icon_num, const gchar *iconfile);
-void qq_prepare_modify_info(PurpleConnection *gc);
-void qq_process_modify_info_reply(guint8 *data, gint data_len, PurpleConnection *gc);
-void qq_process_get_buddy_info(guint8 *data, gint data_len, PurpleConnection *gc);
-void qq_info_query_free(qq_data *qd);
+		gint update_class, int action);
+void qq_set_custom_icon(PurpleConnection *gc, PurpleStoredImage *img);
+void qq_process_change_info(PurpleConnection *gc, guint8 *data, gint data_len);
+void qq_process_get_buddy_info(guint8 *data, gint data_len, guint32 action, PurpleConnection *gc);
+
 void qq_request_get_level(PurpleConnection *gc, guint32 uid);
+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);
 #endif
--- a/libpurple/protocols/qq/buddy_list.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/buddy_list.c	Thu Nov 06 03:20:05 2008 +0000
@@ -34,10 +34,9 @@
 #include "buddy_list.h"
 #include "buddy_opt.h"
 #include "char_conv.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "qq_base.h"
 #include "group.h"
-#include "group_find.h"
 #include "group_internal.h"
 #include "group_info.h"
 
@@ -78,16 +77,18 @@
 	bytes += qq_put16(raw_data + bytes, 0x0000);
 
 	qq_send_cmd_mess(gc, QQ_CMD_GET_BUDDIES_ONLINE, raw_data, 5, update_class, 0);
-	qd->last_get_online = time(NULL);
 }
 
 /* position starts with 0x0000,
  * server may return a position tag if list is too long for one packet */
-void qq_request_get_buddies_list(PurpleConnection *gc, guint16 position, gint update_class)
+void qq_request_get_buddies(PurpleConnection *gc, guint16 position, gint update_class)
 {
+	qq_data *qd;
 	guint8 raw_data[16] = {0};
 	gint bytes = 0;
 
+	qd = (qq_data *) gc->proto_data;
+
 	/* 000-001 starting position, can manually specify */
 	bytes += qq_put16(raw_data + bytes, position);
 	/* before Mar 18, 2004, any value can work, and we sent 00
@@ -96,6 +97,9 @@
 	 * Now I tested that 00,00,00,00,00,01 work perfectly
 	 * March 22, found the 00,00,00 starts to work as well */
 	bytes += qq_put8(raw_data + bytes, 0x00);
+	if (qd->client_version >= 2007) {
+		bytes += qq_put16(raw_data + bytes, 0x0000);
+	}
 
 	qq_send_cmd_mess(gc, QQ_CMD_GET_BUDDIES_LIST, raw_data, bytes, update_class, 0);
 }
@@ -139,7 +143,7 @@
 	bytes += qq_get8(&bs->unknown2, data + bytes);
 	/* 012-012: status */
 	bytes += qq_get8(&bs->status, data + bytes);
-	/* 013-014: client_version */
+	/* 013-014: client tag */
 	bytes += qq_get16(&bs->unknown3, data + bytes);
 	/* 015-030: unknown key */
 	bytes += qq_getdata(&(bs->unknown_key[0]), QQ_KEY_LENGTH, data + bytes);
@@ -152,117 +156,118 @@
 	return bytes;
 }
 
-#define QQ_ONLINE_BUDDY_ENTRY_LEN       38
-
 /* process the reply packet for get_buddies_online packet */
-guint8 qq_process_get_buddies_online_reply(guint8 *data, gint data_len, PurpleConnection *gc)
+guint8 qq_process_get_buddies_online(guint8 *data, gint data_len, PurpleConnection *gc)
 {
 	qq_data *qd;
-	gint bytes, bytes_buddy;
+	gint bytes, bytes_start;
 	gint count;
 	guint8  position;
-	PurpleBuddy *b;
-	qq_buddy *q_bud;
-	qq_buddy_online bo;
-	gchar *purple_name;
+	qq_buddy_data *bd;
+	int entry_len = 38;
+
+	qq_buddy_status bs;
+	struct {
+		guint16 unknown1;
+		guint8 ext_flag;
+		guint8 comm_flag;
+		guint16 unknown2;
+		guint8 ending;		/* 0x00 */
+	} packet;
 
 	g_return_val_if_fail(data != NULL && data_len != 0, -1);
 
 	qd = (qq_data *) gc->proto_data;
 
 	/* qq_show_packet("Get buddies online reply packet", data, len); */
+	if (qd->client_version >= 2007)	entry_len += 4;
 
 	bytes = 0;
 	bytes += qq_get8(&position, data + bytes);
 
 	count = 0;
 	while (bytes < data_len) {
-		if (data_len - bytes < QQ_ONLINE_BUDDY_ENTRY_LEN) {
-			purple_debug_error("QQ", "[buddies online] only %d, need %d",
-					(data_len - bytes), QQ_ONLINE_BUDDY_ENTRY_LEN);
+		if (data_len - bytes < entry_len) {
+			purple_debug_error("QQ", "[buddies online] only %d, need %d\n",
+					(data_len - bytes), entry_len);
 			break;
 		}
-		memset(&bo, 0 ,sizeof(bo));
+		memset(&bs, 0 ,sizeof(bs));
+		memset(&packet, 0 ,sizeof(packet));
 
 		/* set flag */
-		bytes_buddy = bytes;
+		bytes_start = bytes;
 		/* based on one online buddy entry */
 		/* 000-030 qq_buddy_status */
-		bytes += get_buddy_status(&(bo.bs), data + bytes);
+		bytes += get_buddy_status(&bs, data + bytes);
 		/* 031-032: */
-		bytes += qq_get16(&bo.unknown1, data + bytes);
+		bytes += qq_get16(&packet.unknown1, data + bytes);
 		/* 033-033: ext_flag */
-		bytes += qq_get8(&bo.ext_flag, data + bytes);
+		bytes += qq_get8(&packet.ext_flag, data + bytes);
 		/* 034-034: comm_flag */
-		bytes += qq_get8(&bo.comm_flag, data + bytes);
+		bytes += qq_get8(&packet.comm_flag, data + bytes);
 		/* 035-036: */
-		bytes += qq_get16(&bo.unknown2, data + bytes);
+		bytes += qq_get16(&packet.unknown2, data + bytes);
 		/* 037-037: */
-		bytes += qq_get8(&bo.ending, data + bytes);	/* 0x00 */
+		bytes += qq_get8(&packet.ending, data + bytes);	/* 0x00 */
+		/* skip 4 bytes in qq2007 */
+		if (qd->client_version >= 2007)	bytes += 4;
 
-		if (bo.bs.uid == 0 || (bytes - bytes_buddy) != QQ_ONLINE_BUDDY_ENTRY_LEN) {
+		if (bs.uid == 0 || (bytes - bytes_start) != entry_len) {
 			purple_debug_error("QQ", "uid=0 or entry complete len(%d) != %d",
-					(bytes - bytes_buddy), QQ_ONLINE_BUDDY_ENTRY_LEN);
+					(bytes - bytes_start), entry_len);
 			continue;
 		}	/* check if it is a valid entry */
 
-		if (bo.bs.uid == qd->uid) {
-			purple_debug_warning("QQ", "I am in online list %d\n", bo.bs.uid);
-			continue;
+		if (bs.uid == qd->uid) {
+			purple_debug_warning("QQ", "I am in online list %d\n", bs.uid);
 		}
 
 		/* update buddy information */
-		purple_name = uid_to_purple_name(bo.bs.uid);
-		if (purple_name == NULL) {
+		bd = qq_buddy_data_find(gc, bs.uid);
+		if (bd == NULL) {
 			purple_debug_error("QQ",
-					"Got an online buddy %d, but not find purple name\n", bo.bs.uid);
+					"Got an online buddy %d, but not in my buddy list\n", bs.uid);
 			continue;
 		}
-		b = purple_find_buddy(purple_connection_get_account(gc), purple_name);
-		g_free(purple_name);
-
-		q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
-		if (q_bud == NULL) {
-			purple_debug_error("QQ",
-					"Got an online buddy %d, but not in my buddy list\n", bo.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;
+		*/
+		if (bd->status != bs.status || bd->comm_flag != packet.comm_flag) {
+			bd->status = bs.status;
+			bd->comm_flag = packet.comm_flag;
+			qq_update_buddy_status(gc, bd->uid, bd->status, bd->comm_flag);
 		}
-		/* we find one and update qq_buddy */
-		/*
-		if(0 != fe->s->client_version)
-			q_bud->client_version = fe->s->client_version;
-		*/
-		q_bud->ip.s_addr = bo.bs.ip.s_addr;
-		q_bud->port = bo.bs.port;
-		q_bud->status = bo.bs.status;
-		q_bud->ext_flag = bo.ext_flag;
-		q_bud->comm_flag = bo.comm_flag;
-		qq_update_buddy_contact(gc, q_bud);
+		bd->ip.s_addr = bs.ip.s_addr;
+		bd->port = bs.port;
+		bd->ext_flag = packet.ext_flag;
+		bd->last_update = time(NULL);
 		count++;
 	}
 
 	if(bytes > data_len) {
 		purple_debug_error("QQ",
-				"qq_process_get_buddies_online_reply: Dangerous error! maybe protocol changed, notify developers!\n");
+				"qq_process_get_buddies_online: Dangerous error! maybe protocol changed, notify developers!\n");
 	}
 
 	purple_debug_info("QQ", "Received %d online buddies, nextposition=%u\n",
-							count, (guint) position);
+			count, (guint) position);
 	return position;
 }
 
 
 /* process reply for get_buddies_list */
-guint16 qq_process_get_buddies_list_reply(guint8 *data, gint data_len, PurpleConnection *gc)
+guint16 qq_process_get_buddies(guint8 *data, gint data_len, PurpleConnection *gc)
 {
 	qq_data *qd;
-	qq_buddy *q_bud;
+	qq_buddy_data bd;
 	gint bytes_expected, count;
 	gint bytes, buddy_bytes;
+	gint nickname_len;
 	guint16 position, unknown;
-	guint8 pascal_len;
-	gchar *name;
-	PurpleBuddy *b;
+	PurpleBuddy *buddy;
 
 	g_return_val_if_fail(data != NULL && data_len != 0, -1);
 
@@ -278,60 +283,66 @@
 	/* the following data is buddy list in this packet */
 	count = 0;
 	while (bytes < data_len) {
-		q_bud = g_new0(qq_buddy, 1);
+		memset(&bd, 0, sizeof(bd));
 		/* set flag */
 		buddy_bytes = bytes;
 		/* 000-003: uid */
-		bytes += qq_get32(&q_bud->uid, data + bytes);
+		bytes += qq_get32(&bd.uid, data + bytes);
 		/* 004-005: icon index (1-255) */
-		bytes += qq_get16(&q_bud->face, data + bytes);
+		bytes += qq_get16(&bd.face, data + bytes);
 		/* 006-006: age */
-		bytes += qq_get8(&q_bud->age, data + bytes);
+		bytes += qq_get8(&bd.age, data + bytes);
 		/* 007-007: gender */
-		bytes += qq_get8(&q_bud->gender, data + bytes);
+		bytes += qq_get8(&bd.gender, data + bytes);
+
+		nickname_len = qq_get_vstr(&bd.nickname, QQ_CHARSET_DEFAULT, data + bytes);
+		bytes += nickname_len;
+		qq_filter_str(bd.nickname);
 
-		pascal_len = convert_as_pascal_string(data + bytes, &q_bud->nickname, QQ_CHARSET_DEFAULT);
-		bytes += pascal_len;
-		qq_filter_str(q_bud->nickname);
-
+		/* Fixme: merge following as 32bit flag */
 		bytes += qq_get16(&unknown, data + bytes);
-		bytes += qq_get8(&q_bud->ext_flag, data + bytes);
-		bytes += qq_get8(&q_bud->comm_flag, data + bytes);
+		bytes += qq_get8(&bd.ext_flag, data + bytes);
+		bytes += qq_get8(&bd.comm_flag, data + bytes);
 
-		bytes_expected = 12 + pascal_len;
+		if (qd->client_version >= 2007) {
+			bytes += 4;		/* skip 4 bytes */
+			bytes_expected = 16 + nickname_len;
+		} else {
+			bytes_expected = 12 + nickname_len;
+		}
 
-		if (q_bud->uid == 0 || (bytes - buddy_bytes) != bytes_expected) {
+		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);
-			g_free(q_bud->nickname);
-			g_free(q_bud);
+			g_free(bd.nickname);
 			continue;
 		} else {
 			count++;
 		}
 
 #if 1
-		purple_debug_info("QQ",
-				"buddy [%09d]: ext_flag=0x%02x, comm_flag=0x%02x, nick=%s\n",
-				q_bud->uid, q_bud->ext_flag, q_bud->comm_flag, q_bud->nickname);
+		purple_debug_info("QQ", "buddy [%09d]: ext_flag=0x%02x, comm_flag=0x%02x, nick=%s\n",
+				bd.uid, bd.ext_flag, bd.comm_flag, bd.nickname);
 #endif
 
-		name = uid_to_purple_name(q_bud->uid);
-		b = purple_find_buddy(gc->account, name);
-		g_free(name);
+		buddy = qq_buddy_find_or_new(gc, bd.uid);
+		if (buddy == NULL || buddy->proto_data == NULL) {
+			g_free(bd.nickname);
+			continue;
+		}
+		purple_blist_server_alias_buddy(buddy, bd.nickname);
+		bd.last_update = time(NULL);
+		qq_update_buddy_status(gc, bd.uid, bd.status, bd.comm_flag);
 
-		if (b == NULL) {
-			b = qq_add_buddy_by_recv_packet(gc, q_bud->uid, TRUE, FALSE);
-		}
-
-		b->proto_data = q_bud;
-		qd->buddies = g_list_append(qd->buddies, q_bud);
-		qq_update_buddy_contact(gc, q_bud);
+		g_memmove(buddy->proto_data, &bd, sizeof(qq_buddy_data));
+		/* nickname has been copy to buddy_data do not free
+		   g_free(bd.nickname);
+		*/
 	}
 
 	if(bytes > data_len) {
 		purple_debug_error("QQ",
-				"qq_process_get_buddies_list_reply: Dangerous error! maybe protocol changed, notify developers!");
+				"qq_process_get_buddies: Dangerous error! maybe protocol changed, notify developers!");
 	}
 
 	purple_debug_info("QQ", "Received %d buddies, nextposition=%u\n",
@@ -347,8 +358,8 @@
 	guint8 sub_cmd, reply_code;
 	guint32 unknown, position;
 	guint32 uid;
-	guint8 type, groupid;
-	qq_group *group;
+	guint8 type;
+	qq_room_data *rmd;
 
 	g_return_val_if_fail(data != NULL && data_len != 0, -1);
 
@@ -360,7 +371,7 @@
 
 	bytes += qq_get8(&reply_code, data + bytes);
 	if(0 != reply_code) {
-		purple_debug_warning("QQ", "qq_process_get_buddies_and_rooms, %d", reply_code);
+		purple_debug_warning("QQ", "qq_process_get_buddies_and_rooms, %d\n", reply_code);
 	}
 
 	bytes += qq_get32(&unknown, data + bytes);
@@ -373,29 +384,23 @@
 		bytes += qq_get32(&uid, data + bytes);
 		/* 04: type 0x1:buddy 0x4:Qun */
 		bytes += qq_get8(&type, data + bytes);
-		/* 05: groupid*4 */ /* seems to always be 0 */
-		bytes += qq_get8(&groupid, data + bytes);
-		/*
-		   purple_debug_info("QQ", "groupid: %i\n", groupid);
-		   groupid >>= 2;
-		   */
+		/* 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);
 			continue;
 		}
 		if(0x1 == type) { /* a buddy */
 			/* don't do anything but count - buddies are handled by
-			 * qq_request_get_buddies_list */
+			 * qq_request_get_buddies */
 			++i;
 		} else { /* a group */
-			group = qq_room_search_id(gc, uid);
-			if(group == NULL) {
-				purple_debug_info("QQ",
-					"Not find room id %d in qq_process_get_buddies_and_rooms\n", uid);
-				qq_set_pending_id(&qd->adding_groups_from_server, uid, TRUE);
+			rmd = qq_room_data_find(gc, uid);
+			if(rmd == NULL) {
+				purple_debug_info("QQ", "Unknow room id %d", uid);
+				qq_send_room_cmd_only(gc, QQ_ROOM_CMD_GET_INFO, uid);
 			} else {
-				group->my_role = QQ_ROOM_ROLE_YES;
-				qq_group_refresh(gc, group);
+				rmd->my_role = QQ_ROOM_ROLE_YES;
 			}
 			++j;
 		}
@@ -416,39 +421,34 @@
 /* TODO: figure out what's going on with the IP region. Sometimes I get valid IP addresses,
  * but the port number's weird, other times I get 0s. I get these simultaneously on the same buddy,
  * using different accounts to get info. */
-
-/* check if status means online or offline */
-gboolean is_online(guint8 status)
+static guint8  get_status_from_purple(PurpleConnection *gc)
 {
-	switch(status) {
-		case QQ_BUDDY_ONLINE_NORMAL:
-		case QQ_BUDDY_ONLINE_AWAY:
-		case QQ_BUDDY_ONLINE_INVISIBLE:
-			return TRUE;
-		case QQ_BUDDY_CHANGE_TO_OFFLINE:
-			return FALSE;
-	}
-	return FALSE;
-}
-
-/* Help calculate the correct icon index to tell the server. */
-gint get_icon_offset(PurpleConnection *gc)
-{
+	qq_data *qd;
 	PurpleAccount *account;
 	PurplePresence *presence;
+	guint8 ret;
 
+	qd = (qq_data *) gc->proto_data;
 	account = purple_connection_get_account(gc);
 	presence = purple_account_get_presence(account);
 
 	if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_INVISIBLE)) {
-		return 2;
+		ret = QQ_BUDDY_ONLINE_INVISIBLE;
+	} else if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_UNAVAILABLE))
+	{
+		if (qd->client_version >= 2007) {
+			ret = QQ_BUDDY_ONLINE_BUSY;
+		} else {
+			ret = QQ_BUDDY_ONLINE_INVISIBLE;
+		}
 	} else if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_AWAY)
 			|| purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_EXTENDED_AWAY)
 			|| purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_UNAVAILABLE)) {
-		return 1;
+		ret = QQ_BUDDY_ONLINE_AWAY;
 	} else {
-		return 0;
+		ret = QQ_BUDDY_ONLINE_NORMAL;
 	}
+	return ret;
 }
 
 /* send a packet to change my online status */
@@ -470,37 +470,37 @@
 	if (!qd->is_login)
 		return;
 
-	if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_INVISIBLE)) {
-		away_cmd = QQ_BUDDY_ONLINE_INVISIBLE;
-	} else if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_AWAY)
-			|| purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_EXTENDED_AWAY)
-			|| purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_UNAVAILABLE)) {
-		away_cmd = QQ_BUDDY_ONLINE_AWAY;
-	} else {
-		away_cmd = QQ_BUDDY_ONLINE_NORMAL;
-	}
+	away_cmd = get_status_from_purple(gc);
 
 	misc_status = 0x00000000;
 	fake_video = purple_prefs_get_bool("/plugins/prpl/qq/show_fake_video");
 	if (fake_video)
 		misc_status |= QQ_MISC_STATUS_HAVING_VIIDEO;
 
-	bytes = 0;
-	bytes += qq_put8(raw_data + bytes, away_cmd);
-	bytes += qq_put32(raw_data + bytes, misc_status);
-
+	if (qd->client_version >= 2007) {
+		bytes = 0;
+		bytes += qq_put8(raw_data + bytes, away_cmd);
+		/* status version */
+		bytes += qq_put16(raw_data + bytes, 0);
+		bytes += qq_put16(raw_data + bytes, 0);
+		bytes += qq_put32(raw_data + bytes, misc_status);
+		/* Fixme: custom status message, now is empty */
+		bytes += qq_put16(raw_data + bytes, 0);
+	} else {
+		bytes = 0;
+		bytes += qq_put8(raw_data + bytes, away_cmd);
+		bytes += qq_put32(raw_data + bytes, misc_status);
+	}
 	qq_send_cmd_mess(gc, QQ_CMD_CHANGE_STATUS, raw_data, bytes, update_class, 0);
 }
 
 /* parse the reply packet for change_status */
-void qq_process_change_status_reply(guint8 *data, gint data_len, PurpleConnection *gc)
+void qq_process_change_status(guint8 *data, gint data_len, PurpleConnection *gc)
 {
 	qq_data *qd;
 	gint bytes;
 	guint8 reply;
-	PurpleBuddy *b;
-	qq_buddy *q_bud;
-	gchar *name;
+	qq_buddy_data *bd;
 
 	g_return_if_fail(data != NULL && data_len != 0);
 
@@ -514,12 +514,11 @@
 	}
 
 	/* purple_debug_info("QQ", "Change status OK\n"); */
-	name = uid_to_purple_name(qd->uid);
-	b = purple_find_buddy(gc->account, name);
-	g_free(name);
-	q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
-	if (q_bud != NULL) {
-		qq_update_buddy_contact(gc, q_bud);
+	bd = qq_buddy_data_find(gc, qd->uid);
+	if (bd != NULL) {
+		bd->status = get_status_from_purple(gc);
+		bd->last_update = time(NULL);
+		qq_update_buddy_status(gc, bd->uid, bd->status, bd->comm_flag);
 	}
 }
 
@@ -529,10 +528,8 @@
 	qq_data *qd;
 	gint bytes;
 	guint32 my_uid;
-	PurpleBuddy *b;
-	qq_buddy *q_bud;
+	qq_buddy_data *bd;
 	qq_buddy_status bs;
-	gchar *name;
 
 	g_return_if_fail(data != NULL && data_len != 0);
 
@@ -552,57 +549,46 @@
 	 * QQ_BUDDY_ONLINE_INVISIBLE */
 	bytes += qq_get32(&my_uid, data + bytes);
 
-	name = uid_to_purple_name(bs.uid);
-	b = purple_find_buddy(gc->account, name);
-	g_free(name);
-	q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
-	if (q_bud == NULL) {
+	bd = qq_buddy_data_find(gc, bs.uid);
+	if (bd == NULL) {
 		purple_debug_warning("QQ", "Get status of unknown buddy %d\n", bs.uid);
 		return;
 	}
 
 	if(bs.ip.s_addr != 0) {
-		q_bud->ip.s_addr = bs.ip.s_addr;
-		q_bud->port = bs.port;
+		bd->ip.s_addr = bs.ip.s_addr;
+		bd->port = bs.port;
 	}
-	q_bud->status =bs.status;
+	if (bd->status != bs.status) {
+		bd->status = bs.status;
+		qq_update_buddy_status(gc, bd->uid, bd->status, bd->comm_flag);
+	}
+	bd->last_update = time(NULL);
 
-	if (q_bud->status == QQ_BUDDY_ONLINE_NORMAL && q_bud->level <= 0) {
-		qq_request_get_level(gc, q_bud->uid);
+	if (bd->status == QQ_BUDDY_ONLINE_NORMAL && bd->level <= 0) {
+		if (qd->client_version >= 2007) {
+			qq_request_get_level_2007(gc, bd->uid);
+		} else {
+			qq_request_get_level(gc, bd->uid);
+		}
 	}
-	qq_update_buddy_contact(gc, q_bud);
 }
 
 /*TODO: maybe this should be qq_update_buddy_status() ?*/
-void qq_update_buddy_contact(PurpleConnection *gc, qq_buddy *q_bud)
+void qq_update_buddy_status(PurpleConnection *gc, guint32 uid, guint8 status, guint8 flag)
 {
-	gchar *purple_name;
-	PurpleBuddy *bud;
+	gchar *who;
 	gchar *status_id;
 
-	g_return_if_fail(q_bud != NULL);
-
-	purple_name = uid_to_purple_name(q_bud->uid);
-	if (purple_name == NULL) {
-		purple_debug_error("QQ", "Not find purple name: %d\n", q_bud->uid);
-		return;
-	}
+	g_return_if_fail(uid != 0);
 
-	bud = purple_find_buddy(gc->account, purple_name);
-	if (bud == NULL) {
-		purple_debug_error("QQ", "Not find buddy: %d\n", q_bud->uid);
-		g_free(purple_name);
-		return;
-	}
-
-	purple_blist_server_alias_buddy(bud, q_bud->nickname); /* server */
-	q_bud->last_update = time(NULL);
+	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); */
 	status_id = "available";
-	switch(q_bud->status) {
+	switch(status) {
 	case QQ_BUDDY_OFFLINE:
 		status_id = "offline";
 		break;
@@ -618,42 +604,81 @@
 	case QQ_BUDDY_ONLINE_INVISIBLE:
 		status_id = "invisible";
 		break;
+	case QQ_BUDDY_ONLINE_BUSY:
+		status_id = "busy";
+		break;
 	default:
 		status_id = "invisible";
-		purple_debug_error("QQ", "unknown status: %x\n", q_bud->status);
+		purple_debug_error("QQ", "unknown status: 0x%X\n", status);
 		break;
 	}
-	purple_debug_info("QQ", "buddy %d %s\n", q_bud->uid, status_id);
-	purple_prpl_got_user_status(gc->account, purple_name, status_id, NULL);
+	purple_debug_info("QQ", "Update buddy %s status as %s\n", who, status_id);
+	purple_prpl_got_user_status(gc->account, who, status_id, NULL);
 
-	if (q_bud->comm_flag & QQ_COMM_FLAG_MOBILE && q_bud->status != QQ_BUDDY_OFFLINE)
-		purple_prpl_got_user_status(gc->account, purple_name, "mobile", NULL);
+	if (flag & QQ_COMM_FLAG_MOBILE && status != QQ_BUDDY_OFFLINE)
+		purple_prpl_got_user_status(gc->account, who, "mobile", NULL);
 	else
-		purple_prpl_got_user_status_deactive(gc->account, purple_name, "mobile");
+		purple_prpl_got_user_status_deactive(gc->account, who, "mobile");
 
-	g_free(purple_name);
+	g_free(who);
 }
 
 /* refresh all buddies online/offline,
  * after receiving reply for get_buddies_online packet */
-void qq_refresh_all_buddy_status(PurpleConnection *gc)
+void qq_update_buddyies_status(PurpleConnection *gc)
 {
-	time_t now;
-	GList *list;
 	qq_data *qd;
-	qq_buddy *q_bud;
+	PurpleBuddy *buddy;
+	qq_buddy_data *bd;
+	GSList *buddies, *it;
+	time_t tm_limit = time(NULL);
 
 	qd = (qq_data *) (gc->proto_data);
-	now = time(NULL);
-	list = qd->buddies;
+
+	tm_limit -= QQ_UPDATE_ONLINE_INTERVAL;
+
+	buddies = purple_find_buddies(purple_connection_get_account(gc), NULL);
+	for (it = buddies; it; it = it->next) {
+		buddy = it->data;
+		if (buddy == NULL) continue;
+		if (buddy->proto_data == NULL) continue;
 
-	while (list != NULL) {
-		q_bud = (qq_buddy *) list->data;
-		if (q_bud != NULL && now > q_bud->last_update + QQ_UPDATE_ONLINE_INTERVAL
-				&& q_bud->status != QQ_BUDDY_ONLINE_INVISIBLE) {
-			q_bud->status = QQ_BUDDY_CHANGE_TO_OFFLINE;
-			qq_update_buddy_contact(gc, q_bud);
-		}
-		list = list->next;
+		bd = (qq_buddy_data *)buddy->proto_data;
+		if (bd->uid == 0) continue;
+		if (bd->uid == qd->uid) continue;	/* my status is always online in my buddy list */
+		if (tm_limit < bd->last_update) continue;
+		if (bd->status == QQ_BUDDY_ONLINE_INVISIBLE) continue;
+		if (bd->status == QQ_BUDDY_CHANGE_TO_OFFLINE) continue;
+
+		bd->status = QQ_BUDDY_CHANGE_TO_OFFLINE;
+		bd->last_update = time(NULL);
+		qq_update_buddy_status(gc, bd->uid, bd->status, bd->comm_flag);
 	}
 }
+
+void qq_buddy_data_free_all(PurpleConnection *gc)
+{
+	qq_data *qd;
+	PurpleBuddy *buddy;
+	GSList *buddies, *it;
+	gint count = 0;
+
+	qd = (qq_data *) (gc->proto_data);
+
+	buddies = purple_find_buddies(purple_connection_get_account(gc), NULL);
+	for (it = buddies; it; it = it->next) {
+		buddy = it->data;
+		if (buddy == NULL) continue;
+		if (buddy->proto_data == NULL) continue;
+
+		qq_buddy_data_free(buddy->proto_data);
+		buddy->proto_data = NULL;
+
+		count++;
+	}
+
+	if (count > 0) {
+		purple_debug_info("QQ", "%d buddies' data are freed\n", count);
+	}
+}
+
--- a/libpurple/protocols/qq/buddy_list.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/buddy_list.h	Thu Nov 06 03:20:05 2008 +0000
@@ -40,33 +40,20 @@
 	guint8 unknown_key[QQ_KEY_LENGTH];
 } qq_buddy_status;
 
-enum {
-	QQ_BUDDY_OFFLINE = 0x00,
-	QQ_BUDDY_ONLINE_NORMAL = 10,
-	QQ_BUDDY_CHANGE_TO_OFFLINE = 20,
-	QQ_BUDDY_ONLINE_AWAY = 30,
-	QQ_BUDDY_ONLINE_INVISIBLE = 40
-};
+void qq_request_get_buddies_online(PurpleConnection *gc, guint8 position, gint update_class);
+guint8 qq_process_get_buddies_online(guint8 *data, gint data_len, PurpleConnection *gc);
 
-void qq_request_get_buddies_online(PurpleConnection *gc, guint8 position, gint update_class);
-guint8 qq_process_get_buddies_online_reply(guint8 *data, gint data_len, PurpleConnection *gc);
-
-void qq_request_get_buddies_list(PurpleConnection *gc, guint16 position, gint update_class);
-guint16 qq_process_get_buddies_list_reply(guint8 *data, gint data_len, PurpleConnection *gc);
+void qq_request_get_buddies(PurpleConnection *gc, guint16 position, gint update_class);
+guint16 qq_process_get_buddies(guint8 *data, gint data_len, PurpleConnection *gc);
 
 void qq_request_get_buddies_and_rooms(PurpleConnection *gc, guint32 position, gint update_class);
 guint32 qq_process_get_buddies_and_rooms(guint8 *data, gint data_len, PurpleConnection *gc);
 
-void qq_refresh_all_buddy_status(PurpleConnection *gc);
-
-gboolean is_online(guint8 status);
-
-gint get_icon_offset(PurpleConnection *gc);
-
 void qq_request_change_status(PurpleConnection *gc, gint update_class);
-void qq_process_change_status_reply(guint8 *data, gint data_len, PurpleConnection *gc);
+void qq_process_change_status(guint8 *data, gint data_len, PurpleConnection *gc);
 void qq_process_buddy_change_status(guint8 *data, gint data_len, PurpleConnection *gc);
 
-void qq_refresh_all_buddy_status(PurpleConnection *gc);
-void qq_update_buddy_contact(PurpleConnection *gc, qq_buddy *q_bud);
+void qq_update_buddyies_status(PurpleConnection *gc);
+void qq_update_buddy_status(PurpleConnection *gc, guint32 uid, guint8 status, guint8 flag);
+void qq_buddy_data_free_all(PurpleConnection *gc);
 #endif
--- a/libpurple/protocols/qq/buddy_opt.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/buddy_opt.c	Thu Nov 06 03:20:05 2008 +0000
@@ -26,12 +26,13 @@
 #include "internal.h"
 #include "notify.h"
 #include "request.h"
+#include "privacy.h"
 
 #include "buddy_info.h"
 #include "buddy_list.h"
 #include "buddy_opt.h"
 #include "char_conv.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "im.h"
 #include "qq_base.h"
 #include "packet_parse.h"
@@ -39,12 +40,8 @@
 #include "utils.h"
 
 #define PURPLE_GROUP_QQ_FORMAT          "QQ (%s)"
-#define PURPLE_GROUP_QQ_UNKNOWN         "QQ Unknown"
-#define PURPLE_GROUP_QQ_BLOCKED         "QQ Blocked"
 
-#define QQ_REMOVE_BUDDY_REPLY_OK      0x00
 #define QQ_REMOVE_SELF_REPLY_OK       0x00
-#define QQ_ADD_BUDDY_AUTH_REPLY_OK    0x30	/* ASCII value of "0" */
 
 enum {
 	QQ_MY_AUTH_APPROVE = 0x30,	/* ASCII value of "0" */
@@ -52,24 +49,419 @@
 	QQ_MY_AUTH_REQUEST = 0x32,	/* ASCII value of "2" */
 };
 
-typedef struct _qq_add_buddy_request {
+typedef struct _qq_buddy_req {
+	PurpleConnection *gc;
 	guint32 uid;
-	guint16 seq;
-} qq_add_buddy_request;
+	guint8 *auth;
+	guint8 auth_len;
+} qq_buddy_req;
+
+void add_buddy_authorize_input(PurpleConnection *gc, guint32 uid,
+		guint8 *auth, guint8 auth_len);
+
+static void buddy_req_free(qq_buddy_req *add_req)
+{
+	g_return_if_fail(add_req != NULL);
+	if (add_req->auth) g_free(add_req->auth);
+	g_free(add_req);
+}
+
+static void buddy_req_cancel_cb(qq_buddy_req *add_req, const gchar *msg)
+{
+	g_return_if_fail(add_req != NULL);
+	buddy_req_free(add_req);
+}
+
+PurpleGroup *qq_group_find_or_new(const gchar *group_name)
+{
+	PurpleGroup *g;
+
+	g_return_val_if_fail(group_name != NULL, NULL);
+
+	g = purple_find_group(group_name);
+	if (g == NULL) {
+		g = purple_group_new(group_name);
+		purple_blist_add_group(g, NULL);
+		purple_debug_warning("QQ", "Add new group: %s\n", group_name);
+	}
+
+	return g;
+}
+
+static qq_buddy_data *qq_buddy_data_new(guint32 uid)
+{
+	qq_buddy_data *bd = g_new0(qq_buddy_data, 1);
+	memset(bd, 0, sizeof(qq_buddy_data));
+	bd->uid = uid;
+	bd->status = QQ_BUDDY_OFFLINE;
+	return bd;
+}
+
+qq_buddy_data *qq_buddy_data_find(PurpleConnection *gc, guint32 uid)
+{
+	gchar *who;
+	PurpleBuddy *buddy;
+
+	g_return_val_if_fail(gc != NULL, NULL);
+
+	who = uid_to_purple_name(uid);
+	if (who == NULL)	return NULL;
+	buddy = purple_find_buddy(purple_connection_get_account(gc), who);
+	g_free(who);
+
+	if (buddy == NULL) {
+		purple_debug_error("QQ", "Can not find purple buddy of %d\n", uid);
+		return NULL;
+	}
+	if (buddy->proto_data == NULL) {
+		purple_debug_error("QQ", "Can not find buddy data of %d\n", uid);
+		return NULL;
+	}
+	return (qq_buddy_data *)buddy->proto_data;
+}
+
+void qq_buddy_data_free(qq_buddy_data *bd)
+{
+	g_return_if_fail(bd != NULL);
+
+	if (bd->nickname) g_free(bd->nickname);
+	g_free(bd);
+}
+
+/* create purple buddy without data and display with no-auth icon */
+PurpleBuddy *qq_buddy_new(PurpleConnection *gc, guint32 uid)
+{
+	PurpleBuddy *buddy;
+	PurpleGroup *group;
+	gchar *who;
+	gchar *group_name;
+
+	g_return_val_if_fail(gc->account != NULL && uid != 0, NULL);
+
+	group_name = g_strdup_printf(PURPLE_GROUP_QQ_FORMAT,
+			purple_account_get_username(gc->account));
+	group = qq_group_find_or_new(group_name);
+	if (group == NULL) {
+		purple_debug_error("QQ", "Failed creating group %s\n", group_name);
+		return NULL;
+	}
+
+	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;
+
+	g_free(who);
+
+	purple_blist_add_buddy(buddy, NULL, group, NULL);
+
+	g_free(group_name);
+
+	return buddy;
+}
+
+static void qq_buddy_free(PurpleBuddy *buddy)
+{
+	g_return_if_fail(buddy);
+	if (buddy->proto_data)	{
+		qq_buddy_data_free(buddy->proto_data);
+	}
+	buddy->proto_data = NULL;
+	purple_blist_remove_buddy(buddy);
+}
+
+PurpleBuddy *qq_buddy_find(PurpleConnection *gc, guint32 uid)
+{
+	PurpleBuddy *buddy;
+	gchar *who;
+
+	g_return_val_if_fail(gc->account != NULL && uid != 0, NULL);
+
+	who = uid_to_purple_name(uid);
+	buddy = purple_find_buddy(gc->account, who);
+	g_free(who);
+	return buddy;
+}
+
+PurpleBuddy *qq_buddy_find_or_new(PurpleConnection *gc, guint32 uid)
+{
+	PurpleBuddy *buddy;
+
+	g_return_val_if_fail(gc->account != NULL && uid != 0, NULL);
+
+	buddy = qq_buddy_find(gc, uid);
+	if (buddy == NULL) {
+		buddy = qq_buddy_new(gc, uid);
+		if (buddy == NULL) {
+			return NULL;
+		}
+	}
+
+	if (buddy->proto_data != NULL) {
+		return buddy;
+	}
+
+	buddy->proto_data = qq_buddy_data_new(uid);
+	return buddy;
+}
 
 /* send packet to remove a buddy from my buddy list */
-static void _qq_send_packet_remove_buddy(PurpleConnection *gc, guint32 uid)
+static void request_remove_buddy(PurpleConnection *gc, guint32 uid)
 {
 	gchar uid_str[11];
+	gint bytes;
 
 	g_return_if_fail(uid > 0);
 
 	g_snprintf(uid_str, sizeof(uid_str), "%d", uid);
-	qq_send_cmd(gc, QQ_CMD_DEL_BUDDY, (guint8 *) uid_str, strlen(uid_str));
+	bytes = strlen(uid_str);
+	qq_send_cmd_mess(gc, QQ_CMD_REMOVE_BUDDY, (guint8 *) uid_str, bytes, 0, uid);
+}
+
+static void request_remove_buddy_ex(PurpleConnection *gc,
+		guint32 uid, guint8 *auth, guint8 auth_len)
+{
+	gint bytes;
+	guint8 *raw_data;
+	gchar uid_str[16];
+
+	g_return_if_fail(uid != 0);
+	g_return_if_fail(auth != NULL && auth_len > 0);
+
+	raw_data = g_newa(guint8, auth_len + sizeof(uid_str) );
+	bytes = 0;
+	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);
+	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);
+}
+
+void qq_request_auth_code(PurpleConnection *gc, guint8 cmd, guint16 sub_cmd, guint32 uid)
+{
+	guint8 raw_data[16];
+	gint bytes;
+
+	g_return_if_fail(uid > 0);
+	bytes = 0;
+	bytes += qq_put8(raw_data + bytes, cmd);
+	bytes += qq_put16(raw_data + bytes, sub_cmd);
+	bytes += qq_put32(raw_data + bytes, uid);
+
+	qq_send_cmd_mess(gc, QQ_CMD_AUTH_CODE, raw_data, bytes, 0, uid);
+}
+
+void qq_process_auth_code(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid)
+{
+	qq_data *qd;
+	gint bytes;
+	guint8 cmd, reply;
+	guint16 sub_cmd;
+	guint8 *code = NULL;
+	guint16 code_len = 0;
+
+	g_return_if_fail(data != NULL && data_len != 0);
+	g_return_if_fail(uid != 0);
+
+	qd = (qq_data *) gc->proto_data;
+
+	qq_show_packet("qq_process_auth_code", data, data_len);
+	bytes = 0;
+	bytes += qq_get8(&cmd, data + bytes);
+	bytes += qq_get16(&sub_cmd, data + bytes);
+	bytes += qq_get8(&reply, data + bytes);
+	g_return_if_fail(bytes + 2 <= data_len);
+
+	bytes += qq_get16(&code_len, data + bytes);
+	g_return_if_fail(code_len > 0);
+	g_return_if_fail(bytes + code_len <= data_len);
+	code = g_newa(guint8, code_len);
+	bytes += qq_getdata(code, code_len, data + bytes);
+
+	if (cmd == QQ_AUTH_INFO_BUDDY && sub_cmd == QQ_AUTH_INFO_REMOVE_BUDDY) {
+		request_remove_buddy_ex(gc, uid, code, code_len);
+		return;
+	}
+	if (cmd == QQ_AUTH_INFO_BUDDY && sub_cmd == QQ_AUTH_INFO_ADD_BUDDY) {
+		add_buddy_authorize_input(gc, uid, code, code_len);
+		return;
+	}
+	purple_debug_info("QQ", "Got auth info cmd 0x%x, sub 0x%x, reply 0x%x\n",
+			cmd, sub_cmd, reply);
+}
+
+static void add_buddy_question_cb(qq_buddy_req *add_req, const gchar *text)
+{
+	g_return_if_fail(add_req != NULL);
+	if (add_req->gc == NULL || add_req->uid == 0) {
+		buddy_req_free(add_req);
+		return;
+	}
+
+	qq_request_question(add_req->gc, QQ_QUESTION_ANSWER, add_req->uid, NULL, text);
+	buddy_req_free(add_req);
+}
+
+static void add_buddy_question_input(PurpleConnection *gc, guint32 uid, gchar *question)
+{
+	gchar *who, *msg;
+	qq_buddy_req *add_req;
+	g_return_if_fail(uid != 0);
+
+	add_req = g_new0(qq_buddy_req, 1);
+	add_req->gc = gc;
+	add_req->uid = uid;
+	add_req->auth = NULL;
+	add_req->auth_len = 0;
+
+	who = uid_to_purple_name(uid);
+	msg = g_strdup_printf(_("%d needs Q&A"), uid);
+	purple_request_input(gc, _("Add buddy Q&A"), msg,
+			_("Input answer here"),
+			NULL,
+			TRUE, FALSE, NULL,
+			_("Send"), G_CALLBACK(add_buddy_question_cb),
+			_("Cancel"), G_CALLBACK(buddy_req_cancel_cb),
+			purple_connection_get_account(gc), who, NULL,
+			add_req);
+
+	g_free(msg);
+	g_free(who);
+}
+
+void qq_request_question(PurpleConnection *gc,
+		guint8 cmd, guint32 uid, const gchar *question_utf8, const gchar *answer_utf8)
+{
+	guint8 raw_data[MAX_PACKET_SIZE - 16];
+	gint bytes;
+
+	g_return_if_fail(uid > 0);
+	bytes = 0;
+	bytes += qq_put8(raw_data + bytes, cmd);
+	if (cmd == QQ_QUESTION_GET) {
+		bytes += qq_put8(raw_data + bytes, 0);
+		qq_send_cmd_mess(gc, QQ_CMD_BUDDY_QUESTION, raw_data, bytes, 0, uid);
+		return;
+	}
+	if (cmd == QQ_QUESTION_SET) {
+		bytes += qq_put_vstr(raw_data + bytes, question_utf8, QQ_CHARSET_DEFAULT);
+		bytes += qq_put_vstr(raw_data + bytes, answer_utf8, QQ_CHARSET_DEFAULT);
+		bytes += qq_put8(raw_data + bytes, 0);
+		qq_send_cmd_mess(gc, QQ_CMD_BUDDY_QUESTION, raw_data, bytes, 0, uid);
+		return;
+	}
+	/* Unknow 2 bytes, 0x(00 01) */
+	bytes += qq_put8(raw_data + bytes, 0x00);
+	bytes += qq_put8(raw_data + bytes, 0x01);
+	g_return_if_fail(uid != 0);
+	bytes += qq_put32(raw_data + bytes, uid);
+	if (cmd == QQ_QUESTION_REQUEST) {
+		qq_send_cmd_mess(gc, QQ_CMD_BUDDY_QUESTION, raw_data, bytes, 0, uid);
+		return;
+	}
+	bytes += qq_put_vstr(raw_data + bytes, answer_utf8, QQ_CHARSET_DEFAULT);
+	bytes += qq_put8(raw_data + bytes, 0);
+	qq_send_cmd_mess(gc, QQ_CMD_BUDDY_QUESTION, raw_data, bytes, 0, uid);
+	return;
+}
+
+static void request_add_buddy_by_question(PurpleConnection *gc, guint32 uid,
+	guint8 *code, guint16 code_len)
+{
+	guint8 raw_data[MAX_PACKET_SIZE - 16];
+	gint bytes = 0;
+
+	g_return_if_fail(uid != 0 && code_len > 0);
+
+	bytes = 0;
+	bytes += qq_put8(raw_data + bytes, 0x10);
+	bytes += qq_put32(raw_data + bytes, uid);
+	bytes += qq_put16(raw_data + bytes, 0);
+
+	bytes += qq_put8(raw_data + bytes, 0);
+	bytes += qq_put8(raw_data + bytes, 0);	/* no auth code */
+
+	bytes += qq_put16(raw_data + bytes, code_len);
+	bytes += qq_putdata(raw_data + bytes, code, code_len);
+
+	bytes += qq_put8(raw_data + bytes, 1);	/* ALLOW ADD ME FLAG */
+	bytes += qq_put8(raw_data + bytes, 0);	/* group number? */
+	qq_send_cmd(gc, QQ_CMD_ADD_BUDDY_AUTH_EX, raw_data, bytes);
+}
+
+void qq_process_question(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid)
+{
+	qq_data *qd;
+	gint bytes;
+	guint8 cmd, reply;
+	gchar *question, *answer;
+	guint16 code_len;
+	guint8 *code;
+
+	g_return_if_fail(data != NULL && data_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+
+	qq_show_packet("qq_process_question", data, data_len);
+	bytes = 0;
+	bytes += qq_get8(&cmd, data + bytes);
+	if (cmd == QQ_QUESTION_GET) {
+		bytes += qq_get_vstr(&question, QQ_CHARSET_DEFAULT, data + bytes);
+		bytes += qq_get_vstr(&answer, QQ_CHARSET_DEFAULT, data + bytes);
+		purple_debug_info("QQ", "Get buddy adding Q&A:\n%s\n%s\n", question, answer);
+		g_free(question);
+		g_free(answer);
+		return;
+	}
+	if (cmd == QQ_QUESTION_SET) {
+		bytes += qq_get8(&reply, data + bytes);
+		if (reply == 0) {
+			purple_debug_info("QQ", "Successed setting Q&A\n");
+		} else {
+			purple_debug_warning("QQ", "Failed setting Q&A, reply %d\n", reply);
+		}
+		return;
+	}
+
+	g_return_if_fail(uid != 0);
+	bytes += 2; /* skip 2 bytes, 0x(00 01)*/
+	if (cmd == QQ_QUESTION_REQUEST) {
+		bytes += qq_get8(&reply, data + bytes);
+		if (reply == 0x01) {
+			purple_debug_warning("QQ", "Failed getting question, reply %d\n", reply);
+			return;
+		}
+		bytes += qq_get_vstr(&question, QQ_CHARSET_DEFAULT, data + bytes);
+		purple_debug_info("QQ", "Get buddy question:\n%s\n", question);
+		add_buddy_question_input(gc, uid, question);
+		g_free(question);
+		return;
+	}
+
+	if (cmd == QQ_QUESTION_ANSWER) {
+		bytes += qq_get8(&reply, data + bytes);
+		if (reply == 0x01) {
+			purple_notify_error(gc, _("Add Buddy"), _("Invalid answer."), NULL);
+			return;
+		}
+		bytes += qq_get16(&code_len, data + bytes);
+		g_return_if_fail(code_len > 0);
+		g_return_if_fail(bytes + code_len <= data_len);
+
+		code = g_newa(guint8, code_len);
+		bytes += qq_getdata(code, code_len, data + bytes);
+		request_add_buddy_by_question(gc, uid, code, code_len);
+		return;
+	}
+
+	g_return_if_reached();
 }
 
 /* try to remove myself from someone's buddy list */
-static void _qq_send_packet_remove_self_from(PurpleConnection *gc, guint32 uid)
+static void request_buddy_remove_me(PurpleConnection *gc, guint32 uid)
 {
 	guint8 raw_data[16] = {0};
 	gint bytes = 0;
@@ -78,31 +470,36 @@
 
 	bytes += qq_put32(raw_data + bytes, uid);
 
-	qq_send_cmd(gc, QQ_CMD_REMOVE_SELF, raw_data, bytes);
+	qq_send_cmd_mess(gc, QQ_CMD_REMOVE_ME, raw_data, bytes, 0, uid);
 }
 
 /* try to add a buddy without authentication */
-static void _qq_send_packet_add_buddy(PurpleConnection *gc, guint32 uid)
+static void request_add_buddy_no_auth(PurpleConnection *gc, guint32 uid)
 {
-	qq_data *qd = (qq_data *) gc->proto_data;
-	qq_add_buddy_request *req;
 	gchar uid_str[11];
 
 	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);
-	qq_send_cmd(gc, QQ_CMD_ADD_BUDDY_WO_AUTH, (guint8 *) uid_str, strlen(uid_str));
+	qq_send_cmd_mess(gc, QQ_CMD_ADD_BUDDY_NO_AUTH,
+			(guint8 *) uid_str, strlen(uid_str), 0, uid);
+}
 
-	/* must be set after sending packet to get the correct send_seq */
-	req = g_new0(qq_add_buddy_request, 1);
-	req->seq = qd->send_seq;
-	req->uid = uid;
-	qd->add_buddy_request = g_list_append(qd->add_buddy_request, req);
+static void request_add_buddy_no_auth_ex(PurpleConnection *gc, guint32 uid)
+{
+	guint bytes;
+	guint8 raw_data[16];
+
+	g_return_if_fail(uid != 0);
+
+	bytes = 0;
+	bytes += qq_put32(raw_data + bytes, uid);
+	qq_send_cmd_mess(gc, QQ_CMD_ADD_BUDDY_NO_AUTH_EX, raw_data, bytes, 0, uid);
 }
 
 /* this buddy needs authentication, text conversion is done at lowest level */
-static void _qq_send_packet_buddy_auth(PurpleConnection *gc, guint32 uid, const gchar response, const gchar *text)
+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;
@@ -125,130 +522,196 @@
 		g_free(text_qq);
 	}
 
-	qq_send_cmd(gc, QQ_CMD_BUDDY_AUTH, raw_data, bytes);
+	qq_send_cmd(gc, QQ_CMD_ADD_BUDDY_AUTH, raw_data, bytes);
 }
 
-static void _qq_send_packet_add_buddy_auth_with_gc_and_uid(gc_and_uid *g, const gchar *text)
+static void request_add_buddy_auth_ex(PurpleConnection *gc, guint32 uid,
+	const gchar *text, guint8 *auth, guint8 auth_len)
 {
-	PurpleConnection *gc;
-	guint32 uid;
-	g_return_if_fail(g != NULL);
+	guint8 raw_data[MAX_PACKET_SIZE - 16];
+	gint bytes = 0;
 
-	gc = g->gc;
-	uid = g->uid;
 	g_return_if_fail(uid != 0);
 
-	_qq_send_packet_buddy_auth(gc, uid, QQ_MY_AUTH_REQUEST, text);
-	g_free(g);
+	bytes = 0;
+	bytes += qq_put8(raw_data + bytes, 0x02);
+	bytes += qq_put32(raw_data + bytes, uid);
+	bytes += qq_put16(raw_data + bytes, 0);
+
+	bytes += qq_put8(raw_data + bytes, 0);
+	if (auth == NULL || auth_len <= 0) {
+		bytes += qq_put8(raw_data + bytes, 0);
+	} else {
+		bytes += qq_put8(raw_data + bytes, auth_len);
+		bytes += qq_putdata(raw_data + bytes, auth, auth_len);
+	}
+	bytes += qq_put8(raw_data + bytes, 1);	/* ALLOW ADD ME FLAG */
+	bytes += qq_put8(raw_data + bytes, 0);	/* group number? */
+	bytes += qq_put_vstr(raw_data + bytes, text, QQ_CHARSET_DEFAULT);
+	qq_send_cmd(gc, QQ_CMD_ADD_BUDDY_AUTH_EX, raw_data, bytes);
+}
+
+void qq_process_add_buddy_auth_ex(PurpleConnection *gc, guint8 *data, gint data_len, guint32 ship32)
+{
+	g_return_if_fail(data != NULL && data_len != 0);
+
+	qq_show_packet("qq_process_question", data, data_len);
+}
+
+static void add_buddy_auth_cb(qq_buddy_req *add_req, const gchar *text)
+{
+	qq_data *qd;
+	g_return_if_fail(add_req != NULL);
+	if (add_req->gc == NULL || add_req->uid == 0) {
+		buddy_req_free(add_req);
+		return;
+	}
+
+	qd = (qq_data *)add_req->gc->proto_data;
+	if (qd->client_version > 2005) {
+		request_add_buddy_auth_ex(add_req->gc, add_req->uid,
+				text, add_req->auth, add_req->auth_len);
+	} else {
+		request_add_buddy_auth(add_req->gc, add_req->uid, QQ_MY_AUTH_REQUEST, text);
+	}
+	buddy_req_free(add_req);
 }
 
 /* the real packet to reject and request is sent from here */
-static void _qq_reject_add_request_real(gc_and_uid *g, const gchar *reason)
+static void buddy_add_deny_reason_cb(qq_buddy_req *add_req, const gchar *reason)
 {
-	gint uid;
-	PurpleConnection *gc;
-
-	g_return_if_fail(g != NULL);
+	g_return_if_fail(add_req != NULL);
+	if (add_req->gc == NULL || add_req->uid == 0) {
+		buddy_req_free(add_req);
+		return;
+	}
 
-	gc = g->gc;
-	uid = g->uid;
-	g_return_if_fail(uid != 0);
+	request_add_buddy_auth(add_req->gc, add_req->uid, QQ_MY_AUTH_REJECT, reason);
+	buddy_req_free(add_req);
+}
 
-	_qq_send_packet_buddy_auth(gc, uid, QQ_MY_AUTH_REJECT, reason);
-	g_free(g);
+static void buddy_add_deny_noreason_cb(qq_buddy_req *add_req)
+{
+	buddy_add_deny_reason_cb(add_req, NULL);
 }
 
 /* we approve other's request of adding me as friend */
-void qq_approve_add_request_with_gc_and_uid(gc_and_uid *g)
+static void buddy_add_authorize_cb(gpointer data)
 {
-	gint uid;
-	PurpleConnection *gc;
-
-	g_return_if_fail(g != NULL);
+	qq_buddy_req *add_req = (qq_buddy_req *)data;
 
-	gc = g->gc;
-	uid = g->uid;
-	g_return_if_fail(uid != 0);
+	g_return_if_fail(add_req != NULL);
+	if (add_req->gc == NULL || add_req->uid == 0) {
+		buddy_req_free(add_req);
+		return;
+	}
 
-	_qq_send_packet_buddy_auth(gc, uid, QQ_MY_AUTH_APPROVE, NULL);
-	g_free(g);
-}
-
-void qq_do_nothing_with_gc_and_uid(gc_and_uid *g, const gchar *msg)
-{
-	g_free(g);
+	request_add_buddy_auth(add_req->gc, add_req->uid, QQ_MY_AUTH_APPROVE, NULL);
+	buddy_req_free(add_req);
 }
 
 /* we reject other's request of adding me as friend */
-void qq_reject_add_request_with_gc_and_uid(gc_and_uid *g)
+static void buddy_add_deny_cb(gpointer data)
 {
-	gint uid;
-	gchar *msg1, *msg2;
-	PurpleConnection *gc;
-	gc_and_uid *g2;
-	gchar *nombre;
-
-	g_return_if_fail(g != NULL);
-
-	gc = g->gc;
-	uid = g->uid;
-	g_return_if_fail(uid != 0);
+	qq_buddy_req *add_req = (qq_buddy_req *)data;
+	gchar *who = uid_to_purple_name(add_req->uid);
+	purple_request_input(add_req->gc, NULL, _("Authorization denied message:"),
+			NULL, _("Sorry, You are not my style."), TRUE, FALSE, NULL,
+			_("OK"), G_CALLBACK(buddy_add_deny_reason_cb),
+			_("Cancel"), G_CALLBACK(buddy_add_deny_noreason_cb),
+			purple_connection_get_account(add_req->gc), who, NULL,
+			add_req);
+	g_free(who);
+}
 
-	g_free(g);
-
-	g2 = g_new0(gc_and_uid, 1);
-	g2->gc = gc;
-	g2->uid = uid;
+static void add_buddy_no_auth_cb(qq_buddy_req *add_req)
+{
+	qq_data *qd;
+	g_return_if_fail(add_req != NULL);
+	if (add_req->gc == NULL || add_req->uid == 0) {
+		buddy_req_free(add_req);
+		return;
+	}
 
-	msg1 = g_strdup_printf(_("You rejected %d's request"), uid);
-	msg2 = g_strdup(_("Message:"));
-
-	nombre = uid_to_purple_name(uid);
-	purple_request_input(gc, _("Reject request"), msg1, msg2,
-			_("Sorry, you are not my style..."), TRUE, FALSE,
-			NULL, _("Reject"), G_CALLBACK(_qq_reject_add_request_real), _("Cancel"), NULL,
-			purple_connection_get_account(gc), nombre, NULL,
-			g2);
-	g_free(nombre);
+	qd = (qq_data *) add_req->gc->proto_data;
+	if (qd->client_version > 2005) {
+		request_add_buddy_no_auth_ex(add_req->gc, add_req->uid);
+	} else {
+		request_add_buddy_no_auth(add_req->gc, add_req->uid);
+	}
+	buddy_req_free(add_req);
 }
 
-void qq_add_buddy_with_gc_and_uid(gc_and_uid *g)
+void add_buddy_authorize_input(PurpleConnection *gc, guint32 uid,
+		guint8 *auth, guint8 auth_len)
 {
-	gint uid;
-	PurpleConnection *gc;
-
-	g_return_if_fail(g != NULL);
-
-	gc = g->gc;
-	uid = g->uid;
+	gchar *who, *msg;
+	qq_buddy_req *add_req;
 	g_return_if_fail(uid != 0);
 
-	_qq_send_packet_add_buddy(gc, uid);
-	g_free(g);
+	add_req = g_new0(qq_buddy_req, 1);
+	add_req->gc = gc;
+	add_req->uid = uid;
+	add_req->auth = NULL;
+	add_req->auth_len = 0;
+	if (auth != NULL && auth_len > 0) {
+		add_req->auth = g_new0(guint8, auth_len);
+		g_memmove(add_req->auth, auth, auth_len);
+		add_req->auth_len = auth_len;
+	}
+
+	who = uid_to_purple_name(uid);
+	msg = g_strdup_printf(_("%d needs authentication"), uid);
+	purple_request_input(gc, _("Add buddy authorize"), msg,
+			_("Input request here"),
+			_("Would you be my friend?"),
+			TRUE, FALSE, NULL,
+			_("Send"), G_CALLBACK(add_buddy_auth_cb),
+			_("Cancel"), G_CALLBACK(buddy_req_cancel_cb),
+			purple_connection_get_account(gc), who, NULL,
+			add_req);
+
+	g_free(msg);
+	g_free(who);
 }
 
-void qq_block_buddy_with_gc_and_uid(gc_and_uid *g)
+/* add a buddy and send packet to QQ server
+ * note that when purple load local cached buddy list into its blist
+ * it also calls this funtion, so we have to
+ * define qd->is_login=TRUE AFTER LOGIN */
+void qq_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
 {
+	qq_data *qd;
 	guint32 uid;
-	PurpleConnection *gc;
-	PurpleBuddy buddy;
-	PurpleGroup group;
+
+	g_return_if_fail(NULL != gc && NULL != gc->proto_data);
+	g_return_if_fail(buddy != NULL);
 
-	g_return_if_fail(g != NULL);
+	qd = (qq_data *) gc->proto_data;
+	if (!qd->is_login)
+		return;		/* IMPORTANT ! */
 
-	gc = g->gc;
-	uid = g->uid;
-	g_return_if_fail(uid > 0);
+	uid = purple_name_to_uid(buddy->name);
+	if (uid > 0) {
+		if (qd->client_version > 2005) {
+			request_add_buddy_no_auth_ex(gc, uid);
+		} else {
+			request_add_buddy_no_auth(gc, uid);
+		}
+		return;
+	}
 
-	buddy.name = uid_to_purple_name(uid);
-	group.name = PURPLE_GROUP_QQ_BLOCKED;
+	purple_notify_error(gc, _("QQ Buddy"), _("Add buddy"), _("Invalid QQ Number"));
+	if (buddy == NULL) {
+		return;
+	}
 
-	qq_remove_buddy(gc, &buddy, &group);
-	_qq_send_packet_remove_self_from(gc, uid);
+	purple_debug_info("QQ", "Remove buddy with invalid QQ number %d\n", uid);
+	qq_buddy_free(buddy);
 }
 
 /*  process reply to add_buddy_auth request */
-void qq_process_add_buddy_auth_reply(guint8 *data, gint data_len, PurpleConnection *gc)
+void qq_process_add_buddy_auth(guint8 *data, gint data_len, PurpleConnection *gc)
 {
 	qq_data *qd;
 	gchar **segments, *msg_utf8;
@@ -257,301 +720,576 @@
 
 	qd = (qq_data *) gc->proto_data;
 
-	if (data[0] != QQ_ADD_BUDDY_AUTH_REPLY_OK) {
-		purple_debug_warning("QQ", "Add buddy with auth request failed\n");
-		if (NULL == (segments = split_data(data, data_len, "\x1f", 2))) {
-			return;
-		}
-		msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT);
-		purple_notify_error(gc, NULL, _("Add buddy with auth request failed"), msg_utf8);
-		g_free(msg_utf8);
-	} else {
-		purple_debug_info("QQ", "Add buddy with auth request OK\n");
+	if (data[0] == '0') {
+		purple_debug_info("QQ", "Reply OK for sending authorize\n");
+		return;
 	}
+
+	if (NULL == (segments = split_data(data, data_len, "\x1f", 2))) {
+		purple_notify_error(gc, _("QQ Buddy"), _("Failed sending authorize"), NULL);
+		return;
+	}
+	msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT);
+	purple_notify_error(gc, _("QQ Buddy"), _("Failed sending authorize"), msg_utf8);
+	g_free(msg_utf8);
 }
 
 /* process the server reply for my request to remove a buddy */
-void qq_process_remove_buddy_reply(guint8 *data, gint data_len, PurpleConnection *gc)
+void qq_process_remove_buddy(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid)
 {
-	qq_data *qd;
+	PurpleBuddy *buddy = NULL;
+	gchar *msg;
 
 	g_return_if_fail(data != NULL && data_len != 0);
-
-	qd = (qq_data *) gc->proto_data;
+	g_return_if_fail(uid != 0);
 
-	if (data[0] != QQ_REMOVE_BUDDY_REPLY_OK) {
-		/* there is no reason return from server */
-		purple_debug_warning("QQ", "Remove buddy fails\n");
-		purple_notify_info(gc, _("QQ Buddy"), _("Failed:"),  _("Remove buddy"));
-	} else {		/* if reply */
-		purple_debug_info("QQ", "Remove buddy OK\n");
-		/* TODO: We don't really need to notify the user about this, do we? */
-		purple_notify_info(gc, _("QQ Buddy"), _("Successed:"),  _("Remove buddy"));
+	buddy = qq_buddy_find(gc, uid);
+	if (data[0] != 0) {
+		msg = g_strdup_printf(_("Failed removing buddy %d"), uid);
+		purple_notify_info(gc, _("QQ Buddy"), msg, NULL);
+		g_free(msg);
+	}
+
+	purple_debug_info("QQ", "Reply OK for removing buddy\n");
+	/* remove buddy again */
+	if (buddy != NULL) {
+		qq_buddy_free(buddy);
 	}
 }
 
 /* process the server reply for my request to remove myself from a buddy */
-void qq_process_remove_self_reply(guint8 *data, gint data_len, PurpleConnection *gc)
+void qq_process_buddy_remove_me(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid)
 {
 	qq_data *qd;
+	gchar *msg;
 
 	g_return_if_fail(data != NULL && data_len != 0);
+	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);
+		return;
+	}
+	msg = g_strdup_printf(_("Failed removing me from %d's buddy list"), uid);
+	purple_notify_info(gc, _("QQ Buddy"), msg, NULL);
+	g_free(msg);
+}
+
+void qq_process_add_buddy_no_auth(PurpleConnection *gc,
+		guint8 *data, gint data_len, guint32 uid)
+{
+	qq_data *qd;
+	gchar **segments;
+	gchar *dest_uid, *reply;
+	PurpleBuddy *buddy;
+
+	g_return_if_fail(data != NULL && data_len != 0);
+	g_return_if_fail(uid != 0);
 
 	qd = (qq_data *) gc->proto_data;
 
-	if (data[0] != QQ_REMOVE_SELF_REPLY_OK) {
-		/* there is no reason return from server */
-		purple_debug_warning("QQ", "Remove self fails\n");
-		purple_notify_info(gc, _("QQ Buddy"), _("Failed:"), _("Remove from other's buddy list"));
-	} else {		/* if reply */
-		purple_debug_info("QQ", "Remove from a buddy OK\n");
-		/* TODO: Does the user really need to be notified about this? */
-		purple_notify_info(gc, _("QQ Buddy"), _("Successed:"), _("Remove from other's buddy list"));
-	}
-}
-
-void qq_process_add_buddy_reply(guint8 *data, gint data_len, guint16 seq, PurpleConnection *gc)
-{
-	qq_data *qd;
-	gint for_uid;
-	gchar *msg, **segments, *uid, *reply;
-	GList *list;
-	PurpleBuddy *b;
-	gc_and_uid *g;
-	qq_add_buddy_request *req;
-	gchar *nombre;
-
-	g_return_if_fail(data != NULL && data_len != 0);
-
-	for_uid = 0;
-	qd = (qq_data *) gc->proto_data;
-
-	list = qd->add_buddy_request;
-	while (list != NULL) {
-		req = (qq_add_buddy_request *) list->data;
-		if (req->seq == seq) {	/* reply to this */
-			for_uid = req->uid;
-			qd->add_buddy_request = g_list_remove(qd->add_buddy_request, qd->add_buddy_request->data);
-			g_free(req);
-			break;
-		}
-		list = list->next;
-	}
-
-	if (for_uid == 0) {	/* we have no record for this */
-		purple_debug_error("QQ", "We have no record for add buddy reply [%d], discard\n", seq);
-		return;
-	} else {
-		purple_debug_info("QQ", "Add buddy reply [%d] is for id [%d]\n", seq, for_uid);
-	}
+	purple_debug_info("QQ", "Process buddy add for id [%d]\n", uid);
+	qq_show_packet("buddy_add_no_auth", data, data_len);
 
 	if (NULL == (segments = split_data(data, data_len, "\x1f", 2)))
 		return;
 
-	uid = segments[0];
+	dest_uid = segments[0];
 	reply = segments[1];
-	if (strtol(uid, NULL, 10) != qd->uid) {	/* should not happen */
-		purple_debug_error("QQ", "Add buddy reply is to [%s], not me!", uid);
+	if (strtol(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;
 	}
 
-	if (strtol(reply, NULL, 10) > 0) {	/* need auth */
-		purple_debug_warning("QQ", "Add buddy attempt fails, need authentication\n");
-		nombre = uid_to_purple_name(for_uid);
-		b = purple_find_buddy(gc->account, nombre);
-		if (b != NULL)
-			purple_blist_remove_buddy(b);
-		g = g_new0(gc_and_uid, 1);
-		g->gc = gc;
-		g->uid = for_uid;
-		msg = g_strdup_printf(_("%d needs authentication"), for_uid);
-		purple_request_input(gc, NULL, msg,
-				_("Input request here"), /* TODO: Awkward string to fix post string freeze - standardize auth dialogues? -evands */
-				_("Would you be my friend?"),
-				TRUE, FALSE, NULL, _("Send"),
-				G_CALLBACK
-				(_qq_send_packet_add_buddy_auth_with_gc_and_uid),
-				_("Cancel"), G_CALLBACK(qq_do_nothing_with_gc_and_uid),
-				purple_connection_get_account(gc), nombre, NULL,
-				g);
-		g_free(msg);
-		g_free(nombre);
-	} else {	/* add OK */
-		qq_add_buddy_by_recv_packet(gc, for_uid, TRUE, TRUE);
-		msg = g_strdup_printf(_("Add into %d's buddy list"), for_uid);
-		purple_notify_info(gc, _("QQ Buddy"), _("Successed:"), msg);
-		g_free(msg);
+	if (strtol(reply, NULL, 10) == 0) {
+		/* add OK */
+		qq_buddy_find_or_new(gc, uid);
+
+		qq_request_buddy_info(gc, uid, 0, 0);
+		if (qd->client_version >= 2007) {
+			qq_request_get_level_2007(gc, uid);
+		} else {
+			qq_request_get_level(gc, uid);
+		}
+		qq_request_get_buddies_online(gc, 0, 0);
+
+		purple_debug_info("QQ", "Successed adding into %d's buddy list", uid);
+		g_strfreev(segments);
+		return;
 	}
+
+	/* need auth */
+	purple_debug_warning("QQ", "Failed adding buddy, need authorize\n");
+
+	buddy = qq_buddy_find(gc, uid);
+	if (buddy == NULL) {
+		buddy = qq_buddy_new(gc, uid);
+	}
+	if (buddy != NULL && buddy->proto_data != NULL) {
+		/* Not authorized now, free buddy data */
+		qq_buddy_data_free(buddy->proto_data);
+		buddy->proto_data = NULL;
+	}
+
+	add_buddy_authorize_input(gc, uid, NULL, 0);
 	g_strfreev(segments);
 }
 
-PurpleGroup *qq_get_purple_group(const gchar *group_name)
+void qq_process_add_buddy_no_auth_ex(PurpleConnection *gc,
+		guint8 *data, gint data_len, guint32 uid)
 {
-	PurpleGroup *g;
+	qq_data *qd;
+	gint bytes;
+	guint32 dest_uid;
+	guint8 reply;
+	guint8 auth_type;
 
-	g_return_val_if_fail(group_name != NULL, NULL);
+	g_return_if_fail(data != NULL && data_len >= 5);
+	g_return_if_fail(uid != 0);
+
+	qd = (qq_data *) gc->proto_data;
+
+	purple_debug_info("QQ", "Process buddy add no auth for id [%d]\n", uid);
+	qq_show_packet("buddy_add_no_auth_ex", data, data_len);
 
-	g = purple_find_group(group_name);
-	if (g == NULL) {
-		g = purple_group_new(group_name);
-		purple_blist_add_group(g, NULL);
-		purple_debug_warning("QQ", "Add new group: %s\n", group_name);
+	bytes = 0;
+	bytes += qq_get32(&dest_uid, data + bytes);
+	bytes += qq_get8(&reply, data + bytes);
+
+	g_return_if_fail(dest_uid == uid);
+
+	if (reply == 0x99) {
+		purple_debug_info("QQ", "Successed adding buddy %d\n", uid);
+		qq_buddy_find_or_new(gc, uid);
+
+		qq_request_buddy_info(gc, uid, 0, 0);
+		if (qd->client_version >= 2007) {
+			qq_request_get_level_2007(gc, uid);
+		} else {
+			qq_request_get_level(gc, uid);
+		}
+		qq_request_get_buddies_online(gc, 0, 0);
+		return;
 	}
 
-	return g;
-}
-
-/* we add new buddy, if the received packet is from someone not in my list
- * return the PurpleBuddy that is just created */
-PurpleBuddy *qq_add_buddy_by_recv_packet(PurpleConnection *gc, guint32 uid, gboolean is_known, gboolean create)
-{
-	PurpleAccount *a;
-	PurpleBuddy *b;
-	PurpleGroup *g;
-	qq_data *qd;
-	qq_buddy *q_bud;
-	gchar *name, *group_name;
-
-	a = gc->account;
-	qd = (qq_data *) gc->proto_data;
-	g_return_val_if_fail(a != NULL && uid != 0, NULL);
-
-	group_name = is_known ?
-		g_strdup_printf(PURPLE_GROUP_QQ_FORMAT, purple_account_get_username(a)) : g_strdup(PURPLE_GROUP_QQ_UNKNOWN);
-
-	g = qq_get_purple_group(group_name);
-
-	name = uid_to_purple_name(uid);
-	b = purple_find_buddy(gc->account, name);
-	/* remove old, we can not simply return here
-	 * because there might be old local copy of this buddy */
-	if (b != NULL)
-		purple_blist_remove_buddy(b);
-
-	b = purple_buddy_new(a, name, NULL);
-
-	if (!create)
-		b->proto_data = NULL;
-	else {
-		q_bud = g_new0(qq_buddy, 1);
-		q_bud->uid = uid;
-		b->proto_data = q_bud;
-		qd->buddies = g_list_append(qd->buddies, q_bud);
-		qq_send_packet_get_info(gc, q_bud->uid, FALSE);
-		qq_request_get_buddies_online(gc, 0, 0);
+	if (reply != 0) {
+		purple_debug_info("QQ", "Failed adding buddy %d, Unknow reply 0x%02X\n",
+			uid, reply);
 	}
 
-	purple_blist_add_buddy(b, NULL, g, NULL);
-	purple_debug_warning("QQ", "Add new buddy: [%s]\n", name);
-
-	g_free(name);
-	g_free(group_name);
-
-	return b;
-}
+	/* need auth */
+	g_return_if_fail(data_len > bytes);
+	bytes += qq_get8(&auth_type, data + bytes);
+	purple_debug_warning("QQ", "Adding buddy needs authorize 0x%02X\n", auth_type);
 
-/* add a buddy and send packet to QQ server
- * note that when purple load local cached buddy list into its blist
- * it also calls this funtion, so we have to
- * define qd->is_login=TRUE AFTER serv_finish_login(gc) */
-void qq_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
-{
-	qq_data *qd;
-	guint32 uid;
-	PurpleBuddy *b;
-
-	qd = (qq_data *) gc->proto_data;
-	if (!qd->is_login)
-		return;		/* IMPORTANT ! */
-
-	uid = purple_name_to_uid(buddy->name);
-	if (uid > 0)
-		_qq_send_packet_add_buddy(gc, uid);
-	else {
-		b = purple_find_buddy(gc->account, buddy->name);
-		if (b != NULL)
-			purple_blist_remove_buddy(b);
-		purple_notify_error(gc, NULL,
-				_("QQ Number Error"),
-				_("Invalid QQ Number"));
+	switch (auth_type) {
+		case 0x00:	/* no authorize */
+			break;
+		case 0x01:	/* authorize */
+			qq_request_auth_code(gc, QQ_AUTH_INFO_BUDDY, QQ_AUTH_INFO_ADD_BUDDY, uid);
+			break;
+		case 0x02:	/* disable */
+			break;
+		case 0x03:	/* answer question */
+			qq_request_question(gc, QQ_QUESTION_REQUEST, uid, NULL, NULL);
+			break;
+		default:
+			g_return_if_reached();
+			break;
 	}
+	return;
 }
 
 /* remove a buddy and send packet to QQ server accordingly */
 void qq_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
 {
 	qq_data *qd;
-	PurpleBuddy *b;
-	qq_buddy *q_bud;
 	guint32 uid;
 
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(buddy != NULL);
+
 	qd = (qq_data *) gc->proto_data;
-	uid = purple_name_to_uid(buddy->name);
-
 	if (!qd->is_login)
 		return;
 
-	if (uid > 0)
-		_qq_send_packet_remove_buddy(gc, uid);
+	uid = purple_name_to_uid(buddy->name);
+	if (uid > 0 && uid != qd->uid) {
+		if (qd->client_version > 2005) {
+			qq_request_auth_code(gc, QQ_AUTH_INFO_BUDDY, QQ_AUTH_INFO_REMOVE_BUDDY, uid);
+		} else {
+			request_remove_buddy(gc, uid);
+			request_buddy_remove_me(gc, uid);
+		}
+	}
+
+	if (buddy->proto_data) {
+		qq_buddy_data_free(buddy->proto_data);
+		buddy->proto_data = NULL;
+	} else {
+		purple_debug_warning("QQ", "Empty buddy data of %s\n", buddy->name);
+	}
+
+	/* Do not call purple_blist_remove_buddy,
+	 * otherwise purple segmentation fault */
+}
+
+static void buddy_add_input(PurpleConnection *gc, guint32 uid, gchar *reason)
+{
+	PurpleAccount *account = purple_connection_get_account(gc);
+	qq_buddy_req *add_req;
+	gchar *who;
+
+	g_return_if_fail(uid != 0 && reason != NULL);
+
+	purple_debug_info("QQ", "Buddy %d request adding, msg: %s\n", uid, reason);
+
+	add_req = g_new0(qq_buddy_req, 1);
+	add_req->gc = gc;
+	add_req->uid = uid;
+
+	if (purple_prefs_get_bool("/plugins/prpl/qq/auto_get_authorize_info")) {
+		qq_request_buddy_info(gc, add_req->uid, 0, QQ_BUDDY_INFO_DISPLAY);
+	}
+	who = uid_to_purple_name(add_req->uid);
+
+	purple_account_request_authorization(account,
+	 		who, NULL,
+			NULL, reason,
+			purple_find_buddy(account, who) != NULL,
+			buddy_add_authorize_cb,
+			buddy_add_deny_cb,
+			add_req);
+
+	g_free(who);
+}
+
+/* someone wants to add you to his buddy list */
+static void server_buddy_add_request(PurpleConnection *gc, gchar *from, gchar *to,
+		guint8 *data, gint data_len)
+{
+	guint32 uid;
+	gchar *msg, *reason;
+
+	g_return_if_fail(from != NULL && to != NULL);
+	uid = strtol(from, NULL, 10);
+	g_return_if_fail(uid != 0);
+
+	if (purple_prefs_get_bool("/plugins/prpl/qq/auto_get_authorize_info")) {
+		qq_request_buddy_info(gc, uid, 0, QQ_BUDDY_INFO_DISPLAY);
+	}
+
+	if (data_len <= 0) {
+		reason = g_strdup( _("No reason given") );
+	} else {
+		msg = g_strndup((gchar *)data, data_len);
+		reason = qq_to_utf8(msg, QQ_CHARSET_DEFAULT);
+		if (reason == NULL)	reason = g_strdup( _("Unknown reason") );
+		g_free(msg);
+	}
+
+	buddy_add_input(gc, uid, reason);
+	g_free(reason);
+}
+
+void qq_process_buddy_check_code(PurpleConnection *gc, guint8 *data, gint data_len)
+{
+	qq_data *qd;
+	gint bytes;
+	guint8 cmd;
+	guint8 reply;
+	guint32 uid;
+	guint16 flag1, flag2;
+
+	g_return_if_fail(data != NULL && data_len >= 5);
+	g_return_if_fail(uid != 0);
+
+	qd = (qq_data *) gc->proto_data;
+
+	qq_show_packet("buddy_check_code", data, data_len);
+
+	bytes = 0;
+	bytes += qq_get8(&cmd, data + bytes);		/* 0x03 */
+	bytes += qq_get8(&reply, data + bytes);
+
+	if (reply == 0) {
+		purple_debug_info("QQ", "Failed checking code\n");
+		return;
+	}
+
+	bytes += qq_get32(&uid, data + bytes);
+	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",
+			uid, flag1, flag2);
+	return;
+}
+
+static void request_buddy_check_code(PurpleConnection *gc,
+		gchar *from, guint8 *code, gint code_len)
+{
+	guint8 *raw_data;
+	gint bytes;
+	guint32 uid;
+
+	g_return_if_fail(code != NULL && code_len > 0 && from != NULL);
+
+	uid = strtol(from, NULL, 10);
+	raw_data = g_newa(guint8, code_len + 16);
+	bytes = 0;
+	bytes += qq_put8(raw_data + bytes, 0x03);
+	bytes += qq_put8(raw_data + bytes, 0x01);
+	bytes += qq_put32(raw_data + bytes, uid);
+	bytes += qq_put16(raw_data + bytes, code_len);
+	bytes += qq_putdata(raw_data + bytes, code, code_len);
+
+	qq_send_cmd(gc, QQ_CMD_BUDDY_CHECK_CODE, raw_data, bytes);
+}
+
+static gint server_buddy_check_code(PurpleConnection *gc,
+		gchar *from, guint8 *data, gint data_len)
+{
+	gint bytes;
+	guint16 code_len;
+	guint8 *code;
+
+	g_return_val_if_fail(data != NULL && data_len > 0, 0);
+
+	bytes = 0;
+	bytes += qq_get16(&code_len, data + bytes);
+	if (code_len <= 0) {
+		purple_debug_info("QQ", "Server msg for buddy has no code\n");
+		return bytes;
+	}
+	if (bytes + code_len < data_len) {
+		purple_debug_error("QQ", "Code len error in server msg for buddy\n");
+		qq_show_packet("server_buddy_check_code", data, data_len);
+		code_len = data_len - bytes;
+	}
+	code = g_newa(guint8, code_len);
+	bytes += qq_getdata(code, code_len, data + bytes);
+
+	request_buddy_check_code(gc, from, code, code_len);
+	return bytes;
+}
+
+static void server_buddy_add_request_ex(PurpleConnection *gc, gchar *from, gchar *to,
+		guint8 *data, gint data_len)
+{
+	gint bytes;
+	guint32 uid;
+	gchar *msg;
+	guint8 allow_reverse;
 
-	b = purple_find_buddy(gc->account, buddy->name);
-	if (b != NULL) {
-		q_bud = (qq_buddy *) b->proto_data;
-		if (q_bud != NULL)
-			qd->buddies = g_list_remove(qd->buddies, q_bud);
-		else
-			purple_debug_warning("QQ", "We have no qq_buddy record for %s\n", buddy->name);
-		/* remove buddy on blist, this does not trigger qq_remove_buddy again
-		 * do this only if the request comes from block request,
-		 * otherwise purple segmentation fault */
-		if (g_ascii_strcasecmp(group->name, PURPLE_GROUP_QQ_BLOCKED) == 0)
-			purple_blist_remove_buddy(b);
+	g_return_if_fail(from != NULL && to != NULL);
+	g_return_if_fail(data != NULL && data_len >= 3);
+	uid = strtol(from, NULL, 10);
+	g_return_if_fail(uid != 0);
+
+	/* qq_show_packet("server_buddy_add_request_ex", data, data_len); */
+
+	bytes = 0;
+	bytes += qq_get_vstr(&msg, QQ_CHARSET_DEFAULT, data+bytes);
+	bytes += qq_get8(&allow_reverse, data + bytes);	/* allow_reverse = 0x01, allowed */
+	server_buddy_check_code(gc, from, data + bytes, data_len - bytes);
+
+	if (strlen(msg) <= 0) {
+		g_free(msg);
+		msg = g_strdup( _("No reason given") );
+	}
+	buddy_add_input(gc, uid, msg);
+	g_free(msg);
+}
+
+/* when you are added by a person, QQ server will send sys message */
+static void server_buddy_added(PurpleConnection *gc, gchar *from, gchar *to,
+		guint8 *data, gint data_len)
+{
+	PurpleAccount *account = purple_connection_get_account(gc);
+	PurpleBuddy *buddy;
+	guint32 uid;
+	qq_buddy_req *add_req;
+	gchar *who;
+	gchar *primary;
+
+	g_return_if_fail(from != NULL && to != NULL);
+
+	uid = strtol(from, NULL, 10);
+	who = uid_to_purple_name(uid);
+
+	buddy = purple_find_buddy(account, who);
+	if (buddy != NULL) {
+		purple_account_notify_added(account, from, to, NULL, NULL);
+	}
+
+	add_req = g_new0(qq_buddy_req, 1);
+	add_req->gc = gc;
+	add_req->uid = uid;	/* only need to get value */
+	primary = g_strdup_printf(_("You have been added by %s"), from);
+	purple_request_action(gc, NULL, primary,
+			_("Would you like to add him?"),
+			PURPLE_DEFAULT_ACTION_NONE,
+			purple_connection_get_account(gc), who, NULL,
+			add_req, 2,
+			_("Add"), G_CALLBACK(add_buddy_no_auth_cb),
+			_("Cancel"), G_CALLBACK(buddy_req_cancel_cb));
+
+	g_free(who);
+	g_free(primary);
+}
+
+static void server_buddy_added_ex(PurpleConnection *gc, gchar *from, gchar *to,
+		guint8 *data, gint data_len)
+{
+	gint bytes;
+	guint8 allow_reverse;
+	gchar *msg;
+
+	g_return_if_fail(from != NULL && to != NULL);
+	g_return_if_fail(data != NULL && data_len >= 3);
+
+	qq_show_packet("server_buddy_added_ex", data, data_len);
+
+	bytes = 0;
+	bytes += qq_get_vstr(&msg, QQ_CHARSET_DEFAULT, data+bytes);	/* always empty msg */
+	purple_debug_info("QQ", "Buddy added msg: %s\n", msg);
+	bytes += qq_get8(&allow_reverse, data + bytes);	/* allow_reverse = 0x01, allowed */
+	server_buddy_check_code(gc, from, data + bytes, data_len - bytes);
+
+	g_free(msg);
+}
+
+static void server_buddy_adding_ex(PurpleConnection *gc, gchar *from, gchar *to,
+		guint8 *data, gint data_len)
+{
+	gint bytes;
+	guint8 allow_reverse;
+
+	g_return_if_fail(from != NULL && to != NULL);
+	g_return_if_fail(data != NULL && data_len >= 3);
+
+	qq_show_packet("server_buddy_adding_ex", data, data_len);
+
+	bytes = 0;
+	bytes += qq_get8(&allow_reverse, data + bytes);	/* allow_reverse = 0x01, allowed */
+	server_buddy_check_code(gc, from, data + bytes, data_len - bytes);
+}
+
+/* the buddy approves your request of adding him/her as your friend */
+static void server_buddy_added_me(PurpleConnection *gc, gchar *from, gchar *to,
+		guint8 *data, gint data_len)
+{
+	PurpleAccount *account = purple_connection_get_account(gc);
+	qq_data *qd;
+	guint32 uid;
+
+	g_return_if_fail(from != NULL && to != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+
+	uid = strtol(from, NULL, 10);
+	g_return_if_fail(uid > 0);
+
+	server_buddy_check_code(gc, from, data, data_len);
+
+	qq_buddy_find_or_new(gc, uid);
+	qq_request_buddy_info(gc, uid, 0, 0);
+	qq_request_get_buddies_online(gc, 0, 0);
+	if (qd->client_version >= 2007) {
+		qq_request_get_level_2007(gc, uid);
+	} else {
+		qq_request_get_level(gc, uid);
+	}
+
+	purple_account_notify_added(account, to, from, NULL, NULL);
+}
+
+/* you are rejected by the person */
+static void server_buddy_rejected_me(PurpleConnection *gc, gchar *from, gchar *to,
+		guint8 *data, gint data_len)
+{
+	guint32 uid;
+	PurpleBuddy *buddy;
+	gchar *msg, *msg_utf8;
+	gint bytes;
+	gchar **segments;
+	gchar *primary, *secondary;
+
+	g_return_if_fail(from != NULL && to != NULL);
+
+	qq_show_packet("server_buddy_rejected_me", data, data_len);
+
+	if (data_len <= 0) {
+		msg = g_strdup( _("No reason given") );
+	} else {
+		segments = g_strsplit((gchar *)data, "\x1f", 1);
+		if (segments != NULL && segments[0] != NULL) {
+			msg = g_strdup(segments[0]);
+			g_strfreev(segments);
+			bytes = strlen(msg) + 1;
+			if (bytes < data_len) {
+				server_buddy_check_code(gc, from, data + bytes, data_len - bytes);
+			}
+		} else {
+			msg = g_strdup( _("No reason given") );
+		}
+	}
+	msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT);
+	if (msg_utf8 == NULL) {
+		msg_utf8 = g_strdup( _("Unknown reason") );
+	}
+	g_free(msg);
+
+	primary = g_strdup_printf(_("Rejected by %s"), from);
+	secondary = g_strdup_printf(_("Message: %s"), msg_utf8);
+
+	purple_notify_info(gc, _("QQ Buddy"), primary, secondary);
+
+	g_free(msg_utf8);
+	g_free(primary);
+	g_free(secondary);
+
+	uid = strtol(from, NULL, 10);
+	g_return_if_fail(uid != 0);
+
+	buddy = qq_buddy_find(gc, uid);
+	if (buddy != NULL && buddy->proto_data != NULL) {
+		/* Not authorized now, free buddy data */
+		qq_buddy_data_free(buddy->proto_data);
+		buddy->proto_data = NULL;
 	}
 }
 
-/* free add buddy request queue */
-void qq_add_buddy_request_free(qq_data *qd)
+void qq_process_buddy_from_server(PurpleConnection *gc, int funct,
+		gchar *from, gchar *to, guint8 *data, gint data_len)
 {
-	gint count;
-	qq_add_buddy_request *p;
-
-	count = 0;
-	while (qd->add_buddy_request != NULL) {
-		p = (qq_add_buddy_request *) (qd->add_buddy_request->data);
-		qd->add_buddy_request = g_list_remove(qd->add_buddy_request, p);
-		g_free(p);
-		count++;
-	}
-	if (count > 0) {
-		purple_debug_info("QQ", "%d add buddy requests are freed!\n", count);
+	switch (funct) {
+	case QQ_SERVER_BUDDY_ADDED:
+		server_buddy_added(gc, from, to, data, data_len);
+		break;
+	case QQ_SERVER_BUDDY_ADD_REQUEST:
+		server_buddy_add_request(gc, from, to, data, data_len);
+		break;
+	case QQ_SERVER_BUDDY_ADD_REQUEST_EX:
+		server_buddy_add_request_ex(gc, from, to, data, data_len);
+		break;
+	case QQ_SERVER_BUDDY_ADDED_ME:
+		server_buddy_added_me(gc, from, to, data, data_len);
+		break;
+	case QQ_SERVER_BUDDY_REJECTED_ME:
+		server_buddy_rejected_me(gc, from, to, data, data_len);
+		break;
+	case QQ_SERVER_BUDDY_ADDED_EX:
+		server_buddy_added_ex(gc, from, to, data, data_len);
+		break;
+	case QQ_SERVER_BUDDY_ADDING_EX:
+	case QQ_SERVER_BUDDY_ADDED_ANSWER:
+		server_buddy_adding_ex(gc, from, to, data, data_len);
+		break;
+	default:
+		purple_debug_warning("QQ", "Unknow buddy operate (%d) from server\n", funct);
+		break;
 	}
 }
-
-/* free up all qq_buddy */
-void qq_buddies_list_free(PurpleAccount *account, qq_data *qd)
-{
-	gint count;
-	qq_buddy *p;
-	gchar *name;
-	PurpleBuddy *b;
-
-	count = 0;
-	while (qd->buddies) {
-		p = (qq_buddy *) (qd->buddies->data);
-		qd->buddies = g_list_remove(qd->buddies, p);
-		name = uid_to_purple_name(p->uid);
-		b = purple_find_buddy(account, name);
-		if(b != NULL)
-			b->proto_data = NULL;
-		else
-			purple_debug_info("QQ", "qq_buddy %s not found in purple proto_data\n", name);
-		g_free(name);
-
-		g_free(p);
-		count++;
-	}
-	if (count > 0) {
-		purple_debug_info("QQ", "%d qq_buddy structures are freed!\n", count);
-	}
-}
--- a/libpurple/protocols/qq/buddy_opt.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/buddy_opt.h	Thu Nov 06 03:20:05 2008 +0000
@@ -30,33 +30,55 @@
 
 #include "qq.h"
 
-typedef struct _gc_and_uid gc_and_uid;
+enum {
+	QQ_AUTH_INFO_BUDDY = 0x01,
+	QQ_AUTH_INFO_ROOM = 0x02,
 
-struct _gc_and_uid {
-	guint32 uid;
-	PurpleConnection *gc;
+	QQ_AUTH_INFO_ADD_BUDDY = 0x0001,
+	QQ_AUTH_INFO_TEMP_SESSION = 0x0003,
+	QQ_AUTH_INFO_CLUSTER = 0x0002,
+	QQ_AUTH_INFO_REMOVE_BUDDY = 0x0006,
+	QQ_AUTH_INFO_UPDATE_BUDDY_INFO = 0x0007,
+};
+
+enum {
+	QQ_QUESTION_GET = 0x01,
+	QQ_QUESTION_SET = 0x02,
+	QQ_QUESTION_REQUEST = 0x03,		/* get question only*/
+	QQ_QUESTION_ANSWER = 0x04,
 };
 
-void qq_approve_add_request_with_gc_and_uid(gc_and_uid *g);
-void qq_reject_add_request_with_gc_and_uid(gc_and_uid *g);
+void qq_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group);
+void qq_change_buddys_group(PurpleConnection *gc, const char *who,
+		const char *old_group, const char *new_group);
+void qq_remove_buddy_and_me(PurpleBlistNode * node);
+void qq_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group);
 
-void qq_add_buddy_with_gc_and_uid(gc_and_uid *g);
-void qq_block_buddy_with_gc_and_uid(gc_and_uid *g);
-
-void qq_do_nothing_with_gc_and_uid(gc_and_uid *g, const gchar *msg);
+void qq_process_remove_buddy(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid);
+void qq_process_buddy_remove_me(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid);
+void qq_process_add_buddy_no_auth(PurpleConnection *gc,
+		guint8 *data, gint data_len, guint32 uid);
+void qq_process_add_buddy_no_auth_ex(PurpleConnection *gc,
+		guint8 *data, gint data_len, guint32 uid);
+void qq_process_add_buddy_auth(guint8 *data, gint data_len, PurpleConnection *gc);
+void qq_process_buddy_from_server(PurpleConnection *gc, int funct,
+		gchar *from, gchar *to, guint8 *data, gint data_len);
 
-void qq_process_remove_buddy_reply(guint8 *buf, gint buf_len, PurpleConnection *gc);
-void qq_process_remove_self_reply(guint8 *data, gint data_len, PurpleConnection *gc);
-void qq_process_add_buddy_reply(guint8 *data, gint data_len, guint16 seq, PurpleConnection *gc);
-void qq_process_add_buddy_auth_reply(guint8 *data, gint data_len, PurpleConnection *gc);
-PurpleBuddy *qq_add_buddy_by_recv_packet(PurpleConnection *gc, guint32 uid, gboolean is_known, gboolean create);
-void qq_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group);
+void qq_process_buddy_check_code(PurpleConnection *gc, guint8 *data, gint data_len);
+
+void qq_request_auth_code(PurpleConnection *gc, guint8 cmd, guint16 sub_cmd, guint32 uid);
+void qq_process_auth_code(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid);
+void qq_request_question(PurpleConnection *gc,
+		guint8 cmd, guint32 uid, const gchar *question_utf8, const gchar *answer_utf8);
+void qq_process_question(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid);
 
-PurpleGroup *qq_get_purple_group(const gchar *group_name);
+void qq_process_add_buddy_auth_ex(PurpleConnection *gc, guint8 *data, gint data_len, guint32 ship32);
+
+qq_buddy_data *qq_buddy_data_find(PurpleConnection *gc, guint32 uid);
+void qq_buddy_data_free(qq_buddy_data *bd);
 
-void qq_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group);
-void qq_add_buddy_request_free(qq_data *qd);
-
-void qq_buddies_list_free(PurpleAccount *account, qq_data *qd);
-
+PurpleBuddy *qq_buddy_new(PurpleConnection *gc, guint32 uid);
+PurpleBuddy *qq_buddy_find_or_new(PurpleConnection *gc, guint32 uid);
+PurpleBuddy *qq_buddy_find(PurpleConnection *gc, guint32 uid);
+PurpleGroup *qq_group_find_or_new(const gchar *group_name);
 #endif
--- a/libpurple/protocols/qq/char_conv.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/char_conv.c	Thu Nov 06 03:20:05 2008 +0000
@@ -37,7 +37,7 @@
 #define QQ_CHARSET_ENG        "ISO-8859-1"
 
 #define QQ_NULL_MSG           "(NULL)"	/* return this if conversion fails */
-#define QQ_NULL_SMILEY        "(Broken)"	/* return this if smiley 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,
@@ -98,7 +98,7 @@
 }
 
 /* convert a string from from_charset to to_charset, using g_convert */
-static gchar *_my_convert(const gchar *str, gssize len, const gchar *to_charset, const gchar *from_charset)
+static gchar *do_convert(const gchar *str, gssize len, const gchar *to_charset, const gchar *from_charset)
 {
 	GError *error = NULL;
 	gchar *ret;
@@ -128,20 +128,42 @@
  * returns the number of bytes read, return -1 if fatal error
  * the converted UTF-8 will be saved in ret
  */
-gint convert_as_pascal_string(guint8 *data, gchar **ret, const gchar *from_charset)
+gint qq_get_vstr(gchar **ret, const gchar *from_charset, guint8 *data)
 {
 	guint8 len;
 
 	g_return_val_if_fail(data != NULL && from_charset != NULL, -1);
 
 	len = data[0];
-	*ret = _my_convert((gchar *) (data + 1), (gssize) len, UTF8, from_charset);
+	if (len == 0) {
+		*ret = g_strdup("");
+		return 1;
+	}
+	*ret = do_convert((gchar *) (data + 1), (gssize) len, UTF8, from_charset);
 
 	return len + 1;
 }
 
+gint qq_put_vstr(guint8 *buf, const gchar *str_utf8, const gchar *to_charset)
+{
+	gchar *str;
+	guint8 len;
+
+	if (str_utf8 == NULL || (len = strlen(str_utf8)) == 0) {
+		buf[0] = 0;
+		return 1;
+	}
+	str = do_convert(str_utf8, -1, to_charset, UTF8);
+	len = strlen(str_utf8);
+	buf[0] = len;
+	if (len > 0) {
+		memcpy(buf + 1, str, len);
+	}
+	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)
+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;
@@ -211,15 +233,15 @@
 	return ret;
 }
 
-/* two convenience methods, using _my_convert */
+/* two convenience methods, using do_convert */
 gchar *utf8_to_qq(const gchar *str, const gchar *to_charset)
 {
-	return _my_convert(str, -1, to_charset, UTF8);
+	return do_convert(str, -1, to_charset, UTF8);
 }
 
 gchar *qq_to_utf8(const gchar *str, const gchar *from_charset)
 {
-	return _my_convert(str, -1, UTF8, from_charset);
+	return do_convert(str, -1, UTF8, from_charset);
 }
 
 /* QQ uses binary code for smiley, while purple uses strings.
@@ -232,8 +254,10 @@
 
 	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];
@@ -291,5 +315,3 @@
 	}
 	g_strstrip(str);
 }
-
-
--- a/libpurple/protocols/qq/char_conv.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/char_conv.h	Thu Nov 06 03:20:05 2008 +0000
@@ -29,15 +29,15 @@
 
 #define QQ_CHARSET_DEFAULT      "GB18030"
 
-gint convert_as_pascal_string(guint8 *data, gchar **ret, const gchar *from_charset);
+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);
+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);
--- a/libpurple/protocols/qq/file_trans.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/file_trans.c	Thu Nov 06 03:20:05 2008 +0000
@@ -30,7 +30,7 @@
 
 #include "qq_crypt.h"
 #include "file_trans.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "im.h"
 #include "packet_parse.h"
 #include "proxy.h"
@@ -81,7 +81,7 @@
 	const gint QQ_MAX_FILE_MD5_LENGTH = 10002432;
 
 	g_return_if_fail(filename != NULL && md5 != NULL);
-	if (filelen > QQ_MAX_FILE_MD5_LENGTH) 
+	if (filelen > QQ_MAX_FILE_MD5_LENGTH)
 		filelen = QQ_MAX_FILE_MD5_LENGTH;
 
 	fp = fopen(filename, "rb");
@@ -161,7 +161,7 @@
 		fd = open(purple_xfer_get_local_filename(xfer), O_RDONLY);
 		info->buffer = mmap(0, purple_xfer_get_size(xfer), PROT_READ, MAP_PRIVATE, fd, 0);
 	}
-	else 
+	else
 	{
 		fd = open(purple_xfer_get_local_filename(xfer), O_RDWR|O_CREAT, 0644);
 		info->buffer = mmap(0, purple_xfer_get_size(xfer), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FILE, fd, 0);
@@ -248,7 +248,7 @@
 	file_key = _gen_file_key();
 
 	bytes += qq_put8(raw_data + bytes, packet_type);
-	bytes += qq_put16(raw_data + bytes, QQ_CLIENT);
+	bytes += qq_put16(raw_data + bytes, qd->client_tag);
 	bytes += qq_put8(raw_data + bytes, file_key & 0xff);
 	bytes += qq_put32(raw_data + bytes, _encrypt_qq_uid(qd->uid, file_key));
 	bytes += qq_put32(raw_data + bytes, _encrypt_qq_uid(to_uid, file_key));
@@ -266,7 +266,7 @@
 {
 	qq_data *qd;
 	gint bytes, bytes_expected, encrypted_len;
-	guint8 *raw_data, *encrypted_data;
+	guint8 *raw_data, *encrypted;
 	time_t now;
 	ft_info *info;
 
@@ -334,19 +334,19 @@
 		raw_data, bytes,
 		"sending packet[%s]:", qq_get_file_cmd_desc(packet_type));
 
-	encrypted_data = g_newa(guint8, bytes + 16);
-	encrypted_len = qq_encrypt(encrypted_data, raw_data, bytes, info->file_session_key);
+	encrypted = g_newa(guint8, bytes + 16);
+	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, info->file_session_key);
 	/*debug: try to decrypt it */
 
 #if 0
 	guint8 *buf;
 	int buflen;
-	hex_dump = hex_dump_to_str(encrypted_data, encrypted_len);
+	hex_dump = hex_dump_to_str(encrypted, encrypted_len);
 	purple_debug_info("QQ", "encrypted packet: \n%s", hex_dump);
 	g_free(hex_dump);
 	buf = g_newa(guint8, MAX_PACKET_SIZE);
 	buflen = encrypted_len;
-	if (qq_crypt(DECRYPT, encrypted_data, encrypted_len, info->file_session_key, buf, &buflen)) {
+	if (qq_crypt(DECRYPT, encrypted, encrypted_len, info->file_session_key, buf, &buflen)) {
 		purple_debug_info("QQ", "decrypt success\n");
 	   if (buflen == bytes && memcmp(raw_data, buf, buflen) == 0)
 			purple_debug_info("QQ", "checksum ok\n");
@@ -360,11 +360,11 @@
 #endif
 
 	purple_debug_info("QQ", "<== send %s packet\n", qq_get_file_cmd_desc(packet_type));
-	_qq_send_file(gc, encrypted_data, encrypted_len, QQ_FILE_CONTROL_PACKET_TAG, info->to_uid);
+	_qq_send_file(gc, encrypted, encrypted_len, QQ_FILE_CONTROL_PACKET_TAG, info->to_uid);
 }
 
 /* send a file to udp channel with QQ_FILE_DATA_PACKET_TAG */
-static void _qq_send_file_data_packet(PurpleConnection *gc, guint16 packet_type, guint8 sub_type, 
+static void _qq_send_file_data_packet(PurpleConnection *gc, guint16 packet_type, guint8 sub_type,
 		guint32 fragment_index, guint16 seq, guint8 *data, gint len)
 {
 	guint8 *raw_data, filename_md5[QQ_KEY_LENGTH], file_md5[QQ_KEY_LENGTH];
@@ -402,11 +402,11 @@
 					_fill_file_md5(purple_xfer_get_local_filename(qd->xfer),
 							purple_xfer_get_size(qd->xfer),
 							file_md5);
-					
+
 					info->fragment_num = (filesize - 1) / QQ_FILE_FRAGMENT_MAXLEN + 1;
 					info->fragment_len = QQ_FILE_FRAGMENT_MAXLEN;
 
-					purple_debug_info("QQ", 
+					purple_debug_info("QQ",
 							"start transfering data, %d fragments with %d length each\n",
 							info->fragment_num, info->fragment_len);
 					/* Unknown */
@@ -431,7 +431,7 @@
 							filename_len);
 					break;
 				case QQ_FILE_DATA_INFO:
-					purple_debug_info("QQ", 
+					purple_debug_info("QQ",
 							"sending %dth fragment with length %d, offset %d\n",
 							fragment_index, len, (fragment_index-1)*fragment_size);
 					/* bytes += qq_put16(raw_data + bytes, ++(qd->send_seq)); */
@@ -532,7 +532,7 @@
 			decryped_bytes = 0;
 			qq_get_conn_info(info, decrypted_data + decryped_bytes);
 			/* qq_send_file_ctl_packet(gc, QQ_FILE_CMD_PING, fh->sender_uid, 0); */
-			qq_send_file_ctl_packet(gc, QQ_FILE_CMD_SENDER_SAY_HELLO, fh.sender_uid, 0);	
+			qq_send_file_ctl_packet(gc, QQ_FILE_CMD_SENDER_SAY_HELLO, fh.sender_uid, 0);
 			break;
 		case QQ_FILE_CMD_SENDER_SAY_HELLO:
 			/* I'm receiver, if we receive SAY_HELLO from sender, we send back the ACK */
@@ -573,8 +573,8 @@
 	ft_info *info = (ft_info *) xfer->data;
 	guint32 mask;
 
-	purple_debug_info("QQ", 
-			"receiving %dth fragment with length %d, slide window status %o, max_fragment_index %d\n", 
+	purple_debug_info("QQ",
+			"receiving %dth fragment with length %d, slide window status %o, max_fragment_index %d\n",
 			index, len, info->window, info->max_fragment_index);
 	if (info->window == 0 && info->max_fragment_index == 0) {
 		if (_qq_xfer_open_file(purple_xfer_get_local_filename(xfer), "wb", xfer) == -1) {
@@ -605,7 +605,7 @@
 		if (mask & 0x8000) mask = 0x0001;
 		else mask = mask << 1;
 	}
-	purple_debug_info("QQ", "procceed %dth fragment, slide window status %o, max_fragment_index %d\n", 
+	purple_debug_info("QQ", "procceed %dth fragment, slide window status %o, max_fragment_index %d\n",
 			index, info->window, info->max_fragment_index);
 }
 
@@ -650,10 +650,10 @@
 	PurpleXfer *xfer = qd->xfer;
 	ft_info *info = (ft_info *) xfer->data;
 
-	purple_debug_info("QQ", 
-			"receiving %dth fragment ack, slide window status %o, max_fragment_index %d\n", 
+	purple_debug_info("QQ",
+			"receiving %dth fragment ack, slide window status %o, max_fragment_index %d\n",
 			fragment_index, info->window, info->max_fragment_index);
-	if (fragment_index < info->max_fragment_index || 
+	if (fragment_index < info->max_fragment_index ||
 			fragment_index >= info->max_fragment_index + sizeof(info->window)) {
 		purple_debug_info("QQ", "duplicate %dth fragment, drop it!\n", fragment_index+1);
 		return;
@@ -681,7 +681,7 @@
 			info->window &= ~mask;
 
 			buffer = g_newa(guint8, info->fragment_len);
-			readbytes = _qq_xfer_read_file(buffer, info->max_fragment_index + sizeof(info->window), 
+			readbytes = _qq_xfer_read_file(buffer, info->max_fragment_index + sizeof(info->window),
 					info->fragment_len, xfer);
 			if (readbytes > 0)
 				_qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP, QQ_FILE_DATA_INFO,
@@ -692,8 +692,8 @@
 			else mask = mask << 1;
 		}
 	}
-	purple_debug_info("QQ", 
-			"procceed %dth fragment ack, slide window status %o, max_fragment_index %d\n", 
+	purple_debug_info("QQ",
+			"procceed %dth fragment ack, slide window status %o, max_fragment_index %d\n",
 			fragment_index, info->window, info->max_fragment_index);
 }
 
@@ -727,13 +727,13 @@
 					bytes += qq_get32(&info->fragment_num, data + bytes);
 					bytes += qq_get32(&info->fragment_len, data + bytes);
 
-					/* FIXME: We must check the md5 here, 
-					 * if md5 doesn't match we will ignore 
+					/* FIXME: We must check the md5 here,
+					 * if md5 doesn't match we will ignore
 					 * the packet or send sth as error number */
 
 					info->max_fragment_index = 0;
 					info->window = 0;
-					purple_debug_info("QQ", 
+					purple_debug_info("QQ",
 							"start receiving data, %d fragments with %d length each\n",
 							info->fragment_num, info->fragment_len);
 					_qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP_ACK, sub_type,
@@ -743,7 +743,7 @@
 					bytes += qq_get32(&fragment_index, data + bytes);
 					bytes += qq_get32(&fragment_offset, data + bytes);
 					bytes += qq_get16(&fragment_len, data + bytes);
-					purple_debug_info("QQ", 
+					purple_debug_info("QQ",
 							"received %dth fragment with length %d, offset %d\n",
 							fragment_index, fragment_len, fragment_offset);
 
--- a/libpurple/protocols/qq/group.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/group.c	Thu Nov 06 03:20:05 2008 +0000
@@ -30,20 +30,19 @@
 
 #include "group_internal.h"
 #include "group_info.h"
-#include "group_search.h"
+#include "group_join.h"
 #include "utils.h"
 #include "qq_network.h"
-#include "header_info.h"
-#include "group.h"
+#include "qq_define.h"
 
 static void _qq_group_search_callback(PurpleConnection *gc, const gchar *input)
 {
 	guint32 ext_id;
 
 	g_return_if_fail(input != NULL);
-	ext_id = qq_string_to_dec_value(input);
+	ext_id = strtol(input, NULL, 10);
 	/* 0x00000000 means search for demo group */
-	qq_send_cmd_group_search_group(gc, ext_id);
+	qq_request_room_search(gc, ext_id, QQ_ROOM_SEARCH_ONLY);
 }
 
 static void _qq_group_search_cancel_callback(PurpleConnection *gc, const gchar *input)
@@ -98,20 +97,8 @@
 
 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Group ID"), QQ_ROOM_KEY_EXTERNAL_ID, FALSE);
 	fields = g_list_append(fields, f);
-	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Creator"), QQ_ROOM_KEY_CREATOR_UID, FALSE);
-	fields = g_list_append(fields, f);
-	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING,
-				    _("Group Description"), QQ_ROOM_KEY_DESC_UTF8, FALSE);
-	fields = g_list_append(fields, f);
 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", QQ_ROOM_KEY_INTERNAL_ID, TRUE);
 	fields = g_list_append(fields, f);
-	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", QQ_ROOM_KEY_TYPE, TRUE);
-	fields = g_list_append(fields, f);
-	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Auth"), QQ_ROOM_KEY_AUTH_TYPE, TRUE);
-	fields = g_list_append(fields, f);
-	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", QQ_ROOM_KEY_CATEGORY, TRUE);
-	fields = g_list_append(fields, f);
-	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", QQ_ROOM_KEY_TITLE_UTF8, TRUE);
 
 	fields = g_list_append(fields, f);
 	purple_roomlist_set_fields(rl, fields);
@@ -142,43 +129,3 @@
 	purple_roomlist_set_in_progress(list, FALSE);
 	purple_roomlist_unref(list);
 }
-
-/* this should be called upon signin, even when we did not open group chat window */
-void qq_group_init(PurpleConnection *gc)
-{
-	PurpleAccount *account;
-	PurpleChat *chat;
-	PurpleGroup *purple_group;
-	PurpleBlistNode *node;
-	qq_group *group;
-	gint count;
-
-	account = purple_connection_get_account(gc);
-
-	purple_group = purple_find_group(PURPLE_GROUP_QQ_QUN);
-	if (purple_group == NULL) {
-		purple_debug_info("QQ", "We have no QQ Qun\n");
-		return;
-	}
-
-	count = 0;
-	for (node = ((PurpleBlistNode *) purple_group)->child; node != NULL; node = node->next) {
-		if ( !PURPLE_BLIST_NODE_IS_CHAT(node)) {
-			continue;
-		}
-		/* got one */
-		chat = (PurpleChat *) node;
-		if (account != chat->account)	/* not qq account*/
-			continue;
-		group = qq_room_create_by_hashtable(gc, chat->components);
-		if (group == NULL)
-			continue;
-
-		if (group->id <= 0)
-			continue;
-
-		count++;
-	}
-
-	purple_debug_info("QQ", "Load %d QQ Qun configurations\n", count);
-}
--- a/libpurple/protocols/qq/group.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/group.h	Thu Nov 06 03:20:05 2008 +0000
@@ -40,10 +40,10 @@
 	QQ_ROOM_ROLE_ADMIN,
 } qq_room_role;
 
-typedef struct _qq_group {
+typedef struct _qq_room_data qq_room_data;
+struct _qq_room_data {
 	/* all these will be saved when we exit Purple */
 	qq_room_role my_role;	/* my role for this room */
-	gchar *my_role_desc;			/* my role description */
 	guint32 id;
 	guint32 ext_id;
 	guint8 type8;			/* permanent or temporory */
@@ -54,16 +54,14 @@
 	gchar *desc_utf8;
 	/* all these will be loaded from the network */
 	gchar *notice_utf8;	/* group notice by admin */
+
+	gboolean is_got_buddies;
 	GList *members;
-
-	gboolean is_got_info;
-} qq_group;
+};
 
 GList *qq_chat_info(PurpleConnection *gc);
 GHashTable *qq_chat_info_defaults(PurpleConnection *gc, const gchar *chat_name);
 
-void qq_group_init(PurpleConnection *gc);
-
 PurpleRoomlist *qq_roomlist_get_list(PurpleConnection *gc);
 
 void qq_roomlist_cancel(PurpleRoomlist *list);
--- a/libpurple/protocols/qq/group_conv.c	Thu Nov 06 02:21:16 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
-/**
- * @file group_conv.c
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#include <glib.h>
-#include "qq.h"
-
-#include "group_conv.h"
-#include "buddy_list.h"
-#include "header_info.h"
-#include "qq_network.h"
-#include "qq_process.h"
-#include "utils.h"
-
-/* show group conversation window */
-PurpleConversation *qq_room_conv_create(PurpleConnection *gc, qq_group *group)
-{
-	PurpleConversation *conv;
-	qq_data *qd;
-
-	g_return_val_if_fail(group != NULL, NULL);
-	qd = (qq_data *) gc->proto_data;
-
-	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
-			group->title_utf8, purple_connection_get_account(gc));
-	if (conv != NULL)	{
-		/* show only one conversation per group */
-		return conv;
-	}
-
-	serv_got_joined_chat(gc, qd->channel++, group->title_utf8);
-	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, group->title_utf8, purple_connection_get_account(gc));
-	if (conv != NULL) {
-		purple_conv_chat_set_topic(PURPLE_CONV_CHAT(conv), NULL, group->notice_utf8);
-		if (group->is_got_info)
-			qq_send_room_cmd_only(gc, QQ_ROOM_CMD_GET_ONLINES, group->id);
-		else
-			qq_update_room(gc, 0, group->id);
-		return conv;
-	}
-	return NULL;
-}
-
-/* refresh online member in group conversation window */
-void qq_group_conv_refresh_online_member(PurpleConnection *gc, qq_group *group)
-{
-	GList *names, *list, *flags;
-	qq_buddy *member;
-	gchar *member_name, *member_uid;
-	PurpleConversation *conv;
-	gint flag;
-	g_return_if_fail(group != NULL);
-
-	names = NULL;
-	flags = NULL;
-	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
-			group->title_utf8, purple_connection_get_account(gc));
-	if (conv != NULL && group->members != NULL) {
-		list = group->members;
-		while (list != NULL) {
-			member = (qq_buddy *) list->data;
-
-			/* we need unique identifiers for everyone in the chat or else we'll
-			 * run into problems with functions like get_cb_real_name from qq.c */
-			member_name =   (member->nickname != NULL && *(member->nickname) != '\0') ?
-					g_strdup_printf("%s (qq-%u)", member->nickname, member->uid) :
-					g_strdup_printf("(qq-%u)", member->uid);
-			member_uid = g_strdup_printf("(qq-%u)", member->uid);
-
-			flag = 0;
-			/* TYPING to put online above OP and FOUNDER */
-			if (is_online(member->status))
-				flag |= (PURPLE_CBFLAGS_TYPING | PURPLE_CBFLAGS_VOICE);
-			if(1 == (member->role & 1)) flag |= PURPLE_CBFLAGS_OP;
-			if(member->uid == group->creator_uid) flag |= PURPLE_CBFLAGS_FOUNDER;
-
-			if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(conv), member_name))
-			{
-				purple_conv_chat_user_set_flags(PURPLE_CONV_CHAT(conv),
-						member_name,
-						flag);
-			} else if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(conv), member_uid))
-			{
-				purple_conv_chat_user_set_flags(PURPLE_CONV_CHAT(conv),
-						member_uid,
-						flag);
-				purple_conv_chat_rename_user(PURPLE_CONV_CHAT(conv), member_uid, member_name);
-			} else {
-				/* always put it even offline */
-				names = g_list_append(names, member_name);
-				flags = g_list_append(flags, GINT_TO_POINTER(flag));
-			}
-			g_free(member_uid);
-			list = list->next;
-		}
-
-		if (names != NULL && flags != NULL) {
-			purple_conv_chat_add_users(PURPLE_CONV_CHAT(conv), names, NULL, flags, FALSE);
-		}
-	}
-	/* clean up names */
-	while (names != NULL) {
-		member_name = (gchar *) names->data;
-		names = g_list_remove(names, member_name);
-		g_free(member_name);
-	}
-	g_list_free(flags);
-}
--- a/libpurple/protocols/qq/group_conv.h	Thu Nov 06 02:21:16 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-/**
- * @file group_conv.h
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#ifndef _QQ_GROUP_CONV_H_
-#define _QQ_GROUP_CONV_H_
-
-#include "connection.h"
-#include "conversation.h"
-#include "group.h"
-
-PurpleConversation *qq_room_conv_create(PurpleConnection *gc, qq_group *group);
-void qq_group_conv_refresh_online_member(PurpleConnection *gc, qq_group *group);
-
-#endif
--- a/libpurple/protocols/qq/group_find.c	Thu Nov 06 02:21:16 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,249 +0,0 @@
-/**
- * @file group_find.c
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#include "qq.h"
-
-#include "conversation.h"
-#include "debug.h"
-#include "util.h"
-
-#include "group_find.h"
-#include "utils.h"
-
-/* find a qq_buddy by uid, called by im.c */
-qq_buddy *qq_group_find_member_by_uid(qq_group *group, guint32 uid)
-{
-	GList *list;
-	qq_buddy *member;
-	g_return_val_if_fail(group != NULL && uid > 0, NULL);
-
-	list = group->members;
-	while (list != NULL) {
-		member = (qq_buddy *) list->data;
-		if (member->uid == uid)
-			return member;
-		else
-			list = list->next;
-	}
-
-	return NULL;
-}
-
-/* remove a qq_buddy by uid, called by qq_group_opt.c */
-void qq_group_remove_member_by_uid(qq_group *group, guint32 uid)
-{
-	GList *list;
-	qq_buddy *member;
-	g_return_if_fail(group != NULL && uid > 0);
-
-	list = group->members;
-	while (list != NULL) {
-		member = (qq_buddy *) list->data;
-		if (member->uid == uid) {
-			group->members = g_list_remove(group->members, member);
-			return;
-		} else {
-			list = list->next;
-		}
-	}
-}
-
-qq_buddy *qq_group_find_or_add_member(PurpleConnection *gc, qq_group *group, guint32 member_uid)
-{
-	qq_buddy *member, *q_bud;
-	PurpleBuddy *buddy;
-	g_return_val_if_fail(group != NULL && member_uid > 0, NULL);
-
-	member = qq_group_find_member_by_uid(group, member_uid);
-	if (member == NULL) {	/* first appear during my session */
-		member = g_new0(qq_buddy, 1);
-		member->uid = member_uid;
-		buddy = purple_find_buddy(purple_connection_get_account(gc), uid_to_purple_name(member_uid));
-		if (buddy != NULL) {
-			q_bud = (qq_buddy *) buddy->proto_data;
-			if (q_bud != NULL && q_bud->nickname != NULL)
-				member->nickname = g_strdup(q_bud->nickname);
-			else if (buddy->alias != NULL)
-				member->nickname = g_strdup(buddy->alias);
-		}
-		group->members = g_list_append(group->members, member);
-	}
-
-	return member;
-}
-
-/* find a qq_group by chatroom channel */
-qq_group *qq_group_find_by_channel(PurpleConnection *gc, gint channel)
-{
-	PurpleConversation *conv;
-	qq_data *qd;
-	qq_group *group;
-	GList *list;
-
-	qd = (qq_data *) gc->proto_data;
-
-	conv = purple_find_chat(gc, channel);
-	g_return_val_if_fail(conv != NULL, NULL);
-
-	list = qd->groups;
-	group = NULL;
-	while (list != NULL) {
-		group = (qq_group *) list->data;
-		if (group->title_utf8 == NULL) {
-			continue;
-		}
-		if (!g_ascii_strcasecmp(purple_conversation_get_name(conv), group->title_utf8))
-			break;
-		list = list->next;
-	}
-
-	return group;
-}
-
-/* find a qq_group by its id, flag is QQ_INTERNAL_ID or QQ_EXTERNAL_ID */
-qq_group *qq_room_search_ext_id(PurpleConnection *gc, guint32 ext_id)
-{
-	GList *list;
-	qq_group *group;
-	qq_data *qd;
-
-	qd = (qq_data *) gc->proto_data;
-
-	if (qd->groups == NULL || ext_id <= 0)
-		return NULL;
-
-	list = qd->groups;
-	while (list != NULL) {
-		group = (qq_group *) list->data;
-		if (group->ext_id == ext_id) {
-			return group;
-		}
-		list = list->next;
-	}
-
-	return NULL;
-}
-
-qq_group *qq_room_search_id(PurpleConnection *gc, guint32 room_id)
-{
-	GList *list;
-	qq_group *group;
-	qq_data *qd;
-
-	qd = (qq_data *) gc->proto_data;
-
-	if (qd->groups == NULL || room_id <= 0)
-		return NULL;
-
-	list = qd->groups;
-	while (list != NULL) {
-		group = (qq_group *) list->data;
-		if (group->id == room_id) {
-			return group;
-		}
-		list = list->next;
-	}
-
-	return NULL;
-}
-
-qq_group *qq_room_get_next(PurpleConnection *gc, guint32 room_id)
-{
-	GList *list;
-	qq_group *group;
-	qq_data *qd;
-	gboolean is_find = FALSE;
-	
-	qd = (qq_data *) gc->proto_data;
-
-	if (qd->groups == NULL) {
-		return NULL;
-	}
-	
-	 if (room_id <= 0) {
-		return (qq_group *) qd->groups->data;
-	}
-	
-	list = qd->groups;
-	while (list != NULL) {
-		group = (qq_group *) list->data;
-		list = list->next;
-		if (group->id == room_id) {
-			is_find = TRUE;
-			break;
-		}
-	}
-
-	if ( !is_find || list == NULL) {
-		return NULL;
-	}
-
-	return (qq_group *)list->data;
-}
-
-qq_group *qq_room_get_next_conv(PurpleConnection *gc, guint32 room_id)
-{
-	GList *list;
-	qq_group *group;
-	qq_data *qd;
-	gboolean is_find;
-
-	qd = (qq_data *) gc->proto_data;
-
- 	list = qd->groups;
-	if (room_id > 0) {
-		/* search next room */
-		is_find = FALSE;
-		while (list != NULL) {
-			group = (qq_group *) list->data;
-			list = list->next;
-			if (group->id == room_id) {
-				is_find = TRUE;
-				break;
-			}
-		}
-		if ( !is_find || list == NULL) {
-			return NULL;
-		}
-	}
-	
-	is_find = FALSE;
-	while (list != NULL) {
-		group = (qq_group *) list->data;
-		if (group->my_role == QQ_ROOM_ROLE_YES || group->my_role == QQ_ROOM_ROLE_ADMIN) {
-			if (NULL != purple_find_conversation_with_account(
-						PURPLE_CONV_TYPE_CHAT,group->title_utf8, purple_connection_get_account(gc))) {
-				/* In convseration*/
-				is_find = TRUE;
-				break;
-			}
-		}
-		list = list->next;
-	}
-
-	if ( !is_find) {
-		return NULL;
-	}
-	return group;
-}
--- a/libpurple/protocols/qq/group_find.h	Thu Nov 06 02:21:16 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-/**
- * @file group_find.h
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#ifndef _QQ_GROUP_FIND_H_
-#define _QQ_GROUP_FIND_H_
-
-#include <glib.h>
-#include "connection.h"
-#include "group.h"
-
-qq_buddy *qq_group_find_member_by_uid(qq_group *group, guint32 uid);
-void qq_group_remove_member_by_uid(qq_group *group, guint32 uid);
-qq_buddy *qq_group_find_or_add_member(PurpleConnection *gc, qq_group *group, guint32 member_uid);
-qq_group *qq_group_find_by_channel(PurpleConnection *gc, gint channel);
-
-qq_group *qq_room_search_ext_id(PurpleConnection *gc, guint32 ext_id);
-qq_group *qq_room_search_id(PurpleConnection *gc, guint32 room_id);
-
-qq_group *qq_room_get_next(PurpleConnection *gc, guint32 room_id);
-qq_group *qq_room_get_next_conv(PurpleConnection *gc, guint32 room_id);
-
-#endif
--- a/libpurple/protocols/qq/group_free.c	Thu Nov 06 02:21:16 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-/**
- * @file group_free.c
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#include "internal.h"
-
-#include "debug.h"
-
-#include "buddy_list.h"
-#include "group_free.h"
-
-/* gracefully free all members in a group */
-static void qq_group_free_member(qq_group *group)
-{
-	gint i;
-	GList *list;
-	qq_buddy *member;
-
-	g_return_if_fail(group != NULL);
-	i = 0;
-	while (NULL != (list = group->members)) {
-		member = (qq_buddy *) list->data;
-		i++;
-		group->members = g_list_remove(group->members, member);
-		g_free(member->nickname);
-		g_free(member);
-	}
-
-	group->members = NULL;
-}
-
-/* gracefully free the memory for one qq_group */
-void qq_group_free(qq_group *group)
-{
-	g_return_if_fail(group != NULL);
-	qq_group_free_member(group);
-	g_free(group->my_role_desc);
-	g_free(group->title_utf8);
-	g_free(group->desc_utf8);
-	g_free(group->notice_utf8);
-	g_free(group);
-}
-
-void qq_group_free_all(qq_data *qd)
-{
-	qq_group *group;
-	gint count;
-
-	g_return_if_fail(qd != NULL);
-	count = 0;
-	while (qd->groups != NULL) {
-		group = (qq_group *) qd->groups->data;
-		qd->groups = g_list_remove(qd->groups, group);
-		qq_group_free(group);
-		count++;
-	}
-
-	if (count > 0) {
-		purple_debug_info("QQ", "%d rooms are freed\n", count);
-	}
-}
--- a/libpurple/protocols/qq/group_free.h	Thu Nov 06 02:21:16 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-/**
- * @file group_free.h
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#ifndef _QQ_GROUP_FREE_H_
-#define _QQ_GROUP_FREE_H_
-
-#include <glib.h>
-#include "qq.h"
-#include "group.h"
-
-void qq_group_free(qq_group *group);
-void qq_group_free_all(qq_data *qd);
-
-#endif
--- a/libpurple/protocols/qq/group_im.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/group_im.c	Thu Nov 06 03:20:05 2008 +0000
@@ -32,43 +32,143 @@
 #include "util.h"
 
 #include "char_conv.h"
-#include "group_find.h"
 #include "group_internal.h"
 #include "group_info.h"
+#include "group_join.h"
 #include "group_im.h"
 #include "group_opt.h"
-#include "group_conv.h"
 #include "im.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "packet_parse.h"
 #include "qq_network.h"
 #include "qq_process.h"
 #include "utils.h"
 
-typedef struct _qq_recv_group_im {
-	guint32 ext_id;
-	guint8 type8;
-	guint32 member_uid;
-	guint16 msg_seq;
-	time_t send_time;
-	guint16 msg_len;
-	gchar *msg;
-	guint8 *font_attr;
-	gint font_attr_len;
-} qq_recv_group_im;
+/* show group conversation window */
+PurpleConversation *qq_room_conv_open(PurpleConnection *gc, qq_room_data *rmd)
+{
+	PurpleConversation *conv;
+	qq_data *qd;
+	gchar *topic_utf8;
+
+	g_return_val_if_fail(rmd != NULL, NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+			rmd->title_utf8, purple_connection_get_account(gc));
+	if (conv != NULL)	{
+		/* show only one conversation per room */
+		return conv;
+	}
+
+	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);
+		purple_conv_chat_set_topic(PURPLE_CONV_CHAT(conv), NULL, topic_utf8);
+		g_free(topic_utf8);
+
+		if (rmd->is_got_buddies)
+			qq_send_room_cmd_only(gc, QQ_ROOM_CMD_GET_ONLINES, rmd->id);
+		else
+			qq_update_room(gc, 0, rmd->id);
+		return conv;
+	}
+	return NULL;
+}
+
+/* refresh online member in group conversation window */
+void qq_room_conv_set_onlines(PurpleConnection *gc, qq_room_data *rmd)
+{
+	GList *names, *list, *flags;
+	qq_buddy_data *bd;
+	gchar *member_name, *member_uid;
+	PurpleConversation *conv;
+	gint flag;
+	gboolean is_find;
+
+	g_return_if_fail(rmd != NULL);
+
+	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+			rmd->title_utf8, purple_connection_get_account(gc));
+	if (conv == NULL) {
+		purple_debug_warning("QQ", "Conversation \"%s\" is not opened\n", rmd->title_utf8);
+		return;
+	}
+	g_return_if_fail(rmd->members != NULL);
+
+	names = NULL;
+	flags = NULL;
+
+	list = rmd->members;
+	while (list != NULL) {
+		bd = (qq_buddy_data *) list->data;
+
+		/* we need unique identifiers for everyone in the chat or else we'll
+		 * run into problems with functions like get_cb_real_name from qq.c */
+		member_name =   (bd->nickname != NULL && *(bd->nickname) != '\0') ?
+				g_strdup_printf("%s (%u)", bd->nickname, bd->uid) :
+				g_strdup_printf("(%u)", bd->uid);
+		member_uid = g_strdup_printf("(%u)", bd->uid);
+
+		flag = 0;
+		/* TYPING to put online above OP and FOUNDER */
+		if (is_online(bd->status)) flag |= (PURPLE_CBFLAGS_TYPING | PURPLE_CBFLAGS_VOICE);
+		if(1 == (bd->role & 1)) flag |= PURPLE_CBFLAGS_OP;
+		if(bd->uid == rmd->creator_uid) flag |= PURPLE_CBFLAGS_FOUNDER;
+
+		is_find = TRUE;
+		if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(conv), member_name))
+		{
+			purple_conv_chat_user_set_flags(PURPLE_CONV_CHAT(conv),
+					member_name,
+					flag);
+		} else if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(conv), member_uid))
+		{
+			purple_conv_chat_user_set_flags(PURPLE_CONV_CHAT(conv),
+					member_uid,
+					flag);
+			purple_conv_chat_rename_user(PURPLE_CONV_CHAT(conv), member_uid, member_name);
+		} else {
+			is_find = FALSE;
+		}
+		if (!is_find) {
+			/* always put it even offline */
+			names = g_list_append(names, member_name);
+			flags = g_list_append(flags, GINT_TO_POINTER(flag));
+		} else {
+			g_free(member_name);
+		}
+		g_free(member_uid);
+		list = list->next;
+	}
+
+	if (names != NULL && flags != NULL) {
+		purple_conv_chat_add_users(PURPLE_CONV_CHAT(conv), names, NULL, flags, FALSE);
+	}
+
+	/* clean up names */
+	while (names != NULL) {
+		member_name = (gchar *) names->data;
+		names = g_list_remove(names, member_name);
+		g_free(member_name);
+	}
+	g_list_free(flags);
+}
 
 /* send IM to a group */
-void qq_send_packet_group_im(PurpleConnection *gc, qq_group *group, const gchar *msg)
+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(group != NULL && msg != NULL);
+	g_return_if_fail(room_id != 0 && msg != NULL);
 
 	msg_filtered = purple_markup_strip_html(msg);
-	purple_debug_info("QQ_MESG", "Send qun mesg filterd: %s\n", msg_filtered);
+	/* 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;
@@ -85,226 +185,76 @@
 	g_free(msg_filtered);
 
 	if (bytes == data_len)	/* create OK */
-		qq_send_room_cmd(gc, QQ_ROOM_CMD_SEND_MSG, group->id, raw_data, data_len);
+		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_group_cmd_im(guint8 *data, gint len, PurpleConnection *gc)
+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;
 }
 
-/* receive an application to join the group */
-void qq_process_room_msg_apply_join(guint8 *data, gint len, guint32 id, PurpleConnection *gc)
+void qq_room_got_chat_in(PurpleConnection *gc,
+		guint32 room_id, guint32 uid_from, const gchar *msg, time_t in_time)
 {
-	guint32 ext_id, user_uid;
-	guint8 type8;
-	gchar *reason_utf8, *msg, *reason;
-	group_member_opt *g;
-	gchar *nombre;
-	gint bytes = 0;
-
-	g_return_if_fail(id > 0 && data != NULL && len > 0);
-
-	/* FIXME: check length here */
-
-	bytes += qq_get32(&ext_id, data + bytes);
-	bytes += qq_get8(&type8, data + bytes);
-	bytes += qq_get32(&user_uid, data + bytes);
-
-	g_return_if_fail(ext_id > 0 && user_uid > 0);
-
-	bytes += convert_as_pascal_string(data + bytes, &reason_utf8, QQ_CHARSET_DEFAULT);
-
-	msg = g_strdup_printf(_("%d request to join Qun %d"), user_uid, ext_id);
-	reason = g_strdup_printf(_("Message: %s"), reason_utf8);
-
-	g = g_new0(group_member_opt, 1);
-	g->gc = gc;
-	g->id = id;
-	g->member = user_uid;
-
-	nombre = uid_to_purple_name(user_uid);
+	PurpleConversation *conv;
+	qq_buddy_data *bd;
+	qq_room_data *rmd;
+	gchar *from;
 
-	purple_request_action(gc, _("QQ Qun Operation"),
-			msg, reason,
-			PURPLE_DEFAULT_ACTION_NONE,
-			purple_connection_get_account(gc), nombre, NULL,
-			g, 3,
-			_("Approve"),
-			G_CALLBACK
-			(qq_group_approve_application_with_struct),
-			_("Reject"),
-			G_CALLBACK
-			(qq_group_reject_application_with_struct),
-			_("Search"), G_CALLBACK(qq_group_search_application_with_struct));
-
-	g_free(nombre);
-	g_free(reason);
-	g_free(msg);
-	g_free(reason_utf8);
-}
+	g_return_if_fail(gc != NULL && room_id != 0);
 
-/* the request to join a group is rejected */
-void qq_process_room_msg_been_rejected(guint8 *data, gint len, guint32 id, PurpleConnection *gc)
-{
-	guint32 ext_id, admin_uid;
-	guint8 type8;
-	gchar *reason_utf8, *msg, *reason;
-	qq_group *group;
-	gint bytes = 0;
-
-	g_return_if_fail(data != NULL && len > 0);
-
-	/* FIXME: check length here */
+	conv = purple_find_chat(gc, room_id);
+	rmd = qq_room_data_find(gc, room_id);
+	g_return_if_fail(rmd != NULL);
 
-	bytes += qq_get32(&ext_id, data + bytes);
-	bytes += qq_get8(&type8, data + bytes);
-	bytes += qq_get32(&admin_uid, data + bytes);
-
-	g_return_if_fail(ext_id > 0 && admin_uid > 0);
-
-	bytes += convert_as_pascal_string(data + bytes, &reason_utf8, QQ_CHARSET_DEFAULT);
-
-	msg = g_strdup_printf
-		(_("Failed to join Qun %d, operated by admin %d"), ext_id, admin_uid);
-	reason = g_strdup_printf(_("Message: %s"), reason_utf8);
-
-	purple_notify_warning(gc, _("QQ Qun Operation"), msg, reason);
-
-	group = qq_room_search_id(gc, id);
-	if (group != NULL) {
-		group->my_role = QQ_ROOM_ROLE_NO;
-		qq_group_refresh(gc, group);
+	if (conv == NULL && purple_prefs_get_bool("/plugins/prpl/qq/auto_popup_conversation")) {
+		conv = qq_room_conv_open(gc, rmd);
 	}
 
-	g_free(reason);
-	g_free(msg);
-	g_free(reason_utf8);
-}
-
-/* the request to join a group is approved */
-void qq_process_room_msg_been_approved(guint8 *data, gint len, guint32 id, PurpleConnection *gc)
-{
-	guint32 ext_id, admin_uid;
-	guint8 type8;
-	gchar *reason_utf8, *msg;
-	qq_group *group;
-	gint bytes = 0;
-
-	g_return_if_fail(data != NULL && len > 0);
-
-	/* FIXME: check length here */
-
-	bytes += qq_get32(&ext_id, data + bytes);
-	bytes += qq_get8(&type8, data + bytes);
-	bytes += qq_get32(&admin_uid, data + bytes);
-
-	g_return_if_fail(ext_id > 0 && admin_uid > 0);
-	/* it is also a "æ— " here, so do not display */
-	bytes += convert_as_pascal_string(data + bytes, &reason_utf8, QQ_CHARSET_DEFAULT);
-
-	msg = g_strdup_printf
-		(_("Successed to join Qun %d, operated by admin %d"), ext_id, admin_uid);
-
-	purple_notify_warning(gc, _("QQ Qun Operation"), msg, NULL);
-
-	group = qq_room_search_id(gc, id);
-	if (group != NULL) {
-		group->my_role = QQ_ROOM_ROLE_YES;
-		qq_group_refresh(gc, group);
+	if (conv == NULL) {
+		return;
 	}
 
-	g_free(msg);
-	g_free(reason_utf8);
-}
-
-/* process the packet when removed from a group */
-void qq_process_room_msg_been_removed(guint8 *data, gint len, guint32 id, PurpleConnection *gc)
-{
-	guint32 ext_id, uid;
-	guint8 type8;
-	gchar *msg;
-	qq_group *group;
-	gint bytes = 0;
-
-	g_return_if_fail(data != NULL && len > 0);
-
-	/* FIXME: check length here */
-
-	bytes += qq_get32(&ext_id, data + bytes);
-	bytes += qq_get8(&type8, data + bytes);
-	bytes += qq_get32(&uid, data + bytes);
-
-	g_return_if_fail(ext_id > 0 && uid > 0);
-
-	msg = g_strdup_printf(_("[%d] removed from Qun \"%d\""), uid, ext_id);
-	purple_notify_info(gc, _("QQ Qun Operation"), _("Notice:"), msg);
-
-	group = qq_room_search_id(gc, id);
-	if (group != NULL) {
-		group->my_role = QQ_ROOM_ROLE_NO;
-		qq_group_refresh(gc, group);
-	}
-
-	g_free(msg);
-}
+	if (uid_from != 0) {
 
-/* process the packet when added to a group */
-void qq_process_room_msg_been_added(guint8 *data, gint len, guint32 id, PurpleConnection *gc)
-{
-	guint32 ext_id, uid;
-	guint8 type8;
-	qq_group *group;
-	gchar *msg;
-	gint bytes = 0;
-
-	g_return_if_fail(data != NULL && len > 0);
-
-	/* FIXME: check length here */
-
-	bytes += qq_get32(&ext_id, data + bytes);
-	bytes += qq_get8(&type8, data + bytes);
-	bytes += qq_get32(&uid, data + bytes);
-
-	g_return_if_fail(ext_id > 0 && uid > 0);
-
-	msg = g_strdup_printf(_("[%d] added to Qun \"%d\""), uid, ext_id);
-	purple_notify_info(gc, _("QQ Qun Operation"), _("Notice:"), msg);
-
-	group = qq_room_search_id(gc, id);
-	if (group != NULL) {
-		group->my_role = QQ_ROOM_ROLE_YES;
-		qq_group_refresh(gc, group);
-	} else {		/* no such group, try to create a dummy first, and then update */
-		group = qq_group_create_internal_record(gc, id, ext_id, NULL);
-		group->my_role = QQ_ROOM_ROLE_YES;
-		qq_group_refresh(gc, group);
-		qq_update_room(gc, 0, group->id);
-		/* the return of this cmd will automatically update the group in blist */
+		bd = qq_room_buddy_find(rmd, uid_from);
+		if (bd == NULL || bd->nickname == NULL)
+			from = g_strdup_printf("%d", uid_from);
+		else
+			from = g_strdup(bd->nickname);
+	} else {
+		from = g_strdup("");
 	}
-
-	g_free(msg);
+	serv_got_chat_in(gc, room_id, from, 0, msg, in_time);
+	g_free(from);
 }
 
 /* recv an IM from a group chat */
-void qq_process_room_msg_normal(guint8 *data, gint data_len, guint32 id, PurpleConnection *gc, guint16 im_type)
+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, *im_src_name;
-	guint16 unknown;
-	guint32 unknown4;
-	PurpleConversation *conv;
+	gchar *msg_with_purple_smiley, *msg_utf8_encoded;
 	qq_data *qd;
-	qq_buddy *member;
-	qq_group *group;
-	qq_recv_group_im *im_group;
 	gint skip_len;
-	gint bytes = 0;
+	gint bytes ;
+	struct {
+		guint32 ext_id;
+		guint8 type8;
+		guint32 member_uid;
+		guint16 unknown;
+		guint16 msg_seq;
+		time_t send_time;
+		guint32 unknown4;
+		guint16 msg_len;
+		gchar *msg;
+		guint8 *font_attr;
+		gint font_attr_len;
+	} packet;
 
 	g_return_if_fail(data != NULL && data_len > 0);
 
@@ -312,24 +262,22 @@
 
 	qd = (qq_data *) gc->proto_data;
 
-#if 0
-	qq_hex_dump(PURPLE_DEBUG_INFO, "QQ", data, data_len, "group im hex dump");
-#endif
+	/* qq_show_packet("ROOM_IM", data, data_len); */
 
-	im_group = g_newa(qq_recv_group_im, 1);
+	memset(&packet, 0, sizeof(packet));
+	bytes = 0;
+	bytes += qq_get32(&(packet.ext_id), data + bytes);
+	bytes += qq_get8(&(packet.type8), data + bytes);
 
-	bytes += qq_get32(&(im_group->ext_id), data + bytes);
-	bytes += qq_get8(&(im_group->type8), data + bytes);
-
-	if(QQ_RECV_IM_TEMP_QUN_IM == im_type) {
+	if(QQ_MSG_TEMP_QUN_IM == msg_type) {
 		bytes += qq_get32(&(id), data + bytes);
 	}
 
-	bytes += qq_get32(&(im_group->member_uid), bytes + data);
-	bytes += qq_get16(&unknown, data + bytes);	/* 0x0001? */
-	bytes += qq_get16(&(im_group->msg_seq), data + bytes);
-	bytes += qq_getime(&im_group->send_time, data + bytes);
-	bytes += qq_get32(&unknown4, data + bytes);	/* versionID */
+	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
@@ -340,9 +288,8 @@
 	 * 3. font_attr
 	 */
 
-	bytes += qq_get16(&(im_group->msg_len), data + bytes);
-	g_return_if_fail(im_group->msg_len > 0);
-
+	bytes += qq_get16(&(packet.msg_len), data + bytes);
+	g_return_if_fail(packet.msg_len > 0);
 	/*
 	 * 10 bytes from lumaqq
 	 *    contentType = buf.getChar();
@@ -352,50 +299,37 @@
 	 *    buf.getInt();
 	 */
 
-	if(im_type != QQ_RECV_IM_UNKNOWN_QUN_IM)
+	if(msg_type != QQ_MSG_UNKNOWN_QUN_IM)
 		skip_len = 10;
 	else
 		skip_len = 0;
 	bytes += skip_len;
 
-	im_group->msg = g_strdup((gchar *) data + bytes);
-	bytes += strlen(im_group->msg) + 1;
+	/* 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 */
-	im_group->font_attr_len = im_group->msg_len - strlen(im_group->msg) - 1 - skip_len;
-	if (im_group->font_attr_len > 0)
-		im_group->font_attr = g_memdup(data + bytes, im_group->font_attr_len);
-	else
-		im_group->font_attr = NULL;
+	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); */
+	} else {
+		packet.font_attr = NULL;
+	}
 
 	/* group im_group has no flag to indicate whether it has font_attr or not */
-	msg_with_purple_smiley = qq_smiley_to_purple(im_group->msg);
-	if (im_group->font_attr_len > 0)
-		msg_utf8_encoded = qq_encode_to_purple(im_group->font_attr,
-				im_group->font_attr_len, msg_with_purple_smiley);
-	else
+	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);
+	} else {
 		msg_utf8_encoded = qq_to_utf8(msg_with_purple_smiley, QQ_CHARSET_DEFAULT);
-
-	group = qq_room_search_id(gc, id);
-	g_return_if_fail(group != NULL);
+	}
+ 	qq_room_got_chat_in(gc, id, packet.member_uid, msg_utf8_encoded, packet.send_time);
 
-	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, group->title_utf8, purple_connection_get_account(gc));
-	if (conv == NULL && purple_prefs_get_bool("/plugins/prpl/qq/show_room_when_newin")) {
-		conv = qq_room_conv_create(gc, group);
-	}
-
-	if (conv != NULL) {
-		member = qq_group_find_member_by_uid(group, im_group->member_uid);
-		if (member == NULL || member->nickname == NULL)
-			im_src_name = uid_to_purple_name(im_group->member_uid);
-		else
-			im_src_name = g_strdup(member->nickname);
-		serv_got_chat_in(gc,
-				purple_conv_chat_get_id(PURPLE_CONV_CHAT
-					(conv)), im_src_name, 0, msg_utf8_encoded, im_group->send_time);
-		g_free(im_src_name);
-	}
 	g_free(msg_with_purple_smiley);
 	g_free(msg_utf8_encoded);
-	g_free(im_group->msg);
-	g_free(im_group->font_attr);
+	g_free(packet.msg);
+	g_free(packet.font_attr);
 }
--- a/libpurple/protocols/qq/group_im.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/group_im.h	Thu Nov 06 03:20:05 2008 +0000
@@ -27,21 +27,18 @@
 
 #include <glib.h>
 #include "connection.h"
+#include "conversation.h"
 #include "group.h"
 
-void qq_send_packet_group_im(PurpleConnection *gc, qq_group *group, const gchar *msg);
-
-void qq_process_group_cmd_im(guint8 *data, gint len, PurpleConnection *gc);
+PurpleConversation *qq_room_conv_open(PurpleConnection *gc, qq_room_data *rmd);
+void qq_room_conv_set_onlines(PurpleConnection *gc, qq_room_data *rmd);
 
-void qq_process_room_msg_normal(guint8 *data, gint data_len, guint32 id, PurpleConnection *gc, guint16 im_type);
-
-void qq_process_room_msg_apply_join(guint8 *data, gint len, guint32 id, PurpleConnection *gc);
+void qq_room_got_chat_in(PurpleConnection *gc,
+		guint32 room_id, guint32 uid_from, const gchar *msg, time_t in_time);
 
-void qq_process_room_msg_been_rejected(guint8 *data, gint len, guint32 id, PurpleConnection *gc);
-
-void qq_process_room_msg_been_approved(guint8 *data, gint len, guint32 id, PurpleConnection *gc);
+void qq_request_room_send_im(PurpleConnection *gc, guint32 room_id, const gchar *msg);
+void qq_process_room_send_im(PurpleConnection *gc, guint8 *data, gint len);
 
-void qq_process_room_msg_been_removed(guint8 *data, gint len, guint32 id, PurpleConnection *gc);
+void qq_process_room_im(guint8 *data, gint data_len, guint32 id, PurpleConnection *gc, guint16 msg_type);
 
-void qq_process_room_msg_been_added(guint8 *data, gint len, guint32 id, PurpleConnection *gc);
 #endif
--- a/libpurple/protocols/qq/group_info.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/group_info.c	Thu Nov 06 03:20:05 2008 +0000
@@ -28,11 +28,11 @@
 #include "debug.h"
 
 #include "char_conv.h"
-#include "group_find.h"
+#include "group_im.h"
 #include "group_internal.h"
 #include "group_info.h"
 #include "buddy_list.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "packet_parse.h"
 #include "qq_network.h"
 #include "utils.h"
@@ -41,7 +41,7 @@
  * this interval determines if their member info is outdated */
 #define QQ_GROUP_CHAT_REFRESH_NICKNAME_INTERNAL  180
 
-static gboolean check_update_interval(qq_buddy *member)
+static gboolean check_update_interval(qq_buddy_data *member)
 {
 	g_return_val_if_fail(member != NULL, FALSE);
 	return (member->nickname == NULL) ||
@@ -50,32 +50,37 @@
 
 /* this is done when we receive the reply to get_online_members sub_cmd
  * all member are set offline, and then only those in reply packets are online */
-static void set_all_offline(qq_group *group)
+static void set_all_offline(qq_room_data *rmd)
 {
 	GList *list;
-	qq_buddy *member;
-	g_return_if_fail(group != NULL);
+	qq_buddy_data *bd;
+	g_return_if_fail(rmd != NULL);
 
-	list = group->members;
+	list = rmd->members;
 	while (list != NULL) {
-		member = (qq_buddy *) list->data;
-		member->status = QQ_BUDDY_CHANGE_TO_OFFLINE;
+		bd = (qq_buddy_data *) list->data;
+		bd->status = QQ_BUDDY_CHANGE_TO_OFFLINE;
 		list = list->next;
 	}
 }
 
 /* send packet to get info for each group member */
-gint qq_request_room_get_buddies(PurpleConnection *gc, qq_group *group, gint update_class)
+gint qq_request_room_get_buddies(PurpleConnection *gc, guint32 room_id, gint update_class)
 {
 	guint8 *raw_data;
 	gint bytes, num;
 	GList *list;
-	qq_buddy *member;
+	qq_room_data *rmd;
+	qq_buddy_data *bd;
+
+	g_return_val_if_fail(room_id > 0, 0);
 
-	g_return_val_if_fail(group != NULL, 0);
-	for (num = 0, list = group->members; list != NULL; list = list->next) {
-		member = (qq_buddy *) list->data;
-		if (check_update_interval(member))
+	rmd  = qq_room_data_find(gc, room_id);
+	g_return_val_if_fail(rmd != NULL, 0);
+
+	for (num = 0, list = rmd->members; list != NULL; list = list->next) {
+		bd = (qq_buddy_data *) list->data;
+		if (check_update_interval(bd))
 			num++;
 	}
 
@@ -88,37 +93,101 @@
 
 	bytes = 0;
 
-	list = group->members;
+	list = rmd->members;
 	while (list != NULL) {
-		member = (qq_buddy *) list->data;
-		if (check_update_interval(member))
-			bytes += qq_put32(raw_data + bytes, member->uid);
+		bd = (qq_buddy_data *) list->data;
+		if (check_update_interval(bd))
+			bytes += qq_put32(raw_data + bytes, bd->uid);
 		list = list->next;
 	}
 
-	qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_BUDDIES, group->id, raw_data, bytes,
+	qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_BUDDIES, rmd->id, raw_data, bytes,
 			update_class, 0);
 	return num;
 }
 
-void qq_process_room_cmd_get_info(guint8 *data, gint data_len, PurpleConnection *gc)
+static gchar *get_role_desc(qq_room_role role)
+{
+	const char *role_desc;
+	switch (role) {
+	case QQ_ROOM_ROLE_NO:
+		role_desc = _("Not member");
+		break;
+	case QQ_ROOM_ROLE_YES:
+		role_desc = _("Member");
+		break;
+	case QQ_ROOM_ROLE_REQUESTING:
+		role_desc = _("Requesting");
+		break;
+	case QQ_ROOM_ROLE_ADMIN:
+		role_desc = _("Admin");
+		break;
+	default:
+		role_desc = _("Unknown");
+	}
+
+	return g_strdup(role_desc);
+}
+
+static void room_info_display(PurpleConnection *gc, qq_room_data *rmd)
 {
-	qq_group *group;
-	qq_buddy *member;
+	PurpleNotifyUserInfo *room_info;
+	gchar *utf8_value;
+
+	g_return_if_fail(rmd != NULL && rmd->id > 0);
+
+	room_info = purple_notify_user_info_new();
+
+	purple_notify_user_info_add_pair(room_info, _("Title"), rmd->title_utf8);
+	purple_notify_user_info_add_pair(room_info, _("Notice"), rmd->notice_utf8);
+	purple_notify_user_info_add_pair(room_info, _("Detail"), rmd->desc_utf8);
+
+	purple_notify_user_info_add_section_break(room_info);
+
+	utf8_value = g_strdup_printf(("%d"), rmd->creator_uid);
+	purple_notify_user_info_add_pair(room_info, _("Creator"), utf8_value);
+	g_free(utf8_value);
+
+	utf8_value = get_role_desc(rmd->my_role);
+	purple_notify_user_info_add_pair(room_info, _("About me"), utf8_value);
+	g_free(utf8_value);
+
+	utf8_value = g_strdup_printf(("%d"), rmd->category);
+	purple_notify_user_info_add_pair(room_info, _("Category"), utf8_value);
+	g_free(utf8_value);
+
+	utf8_value = g_strdup_printf(("%d"), rmd->auth_type);
+	purple_notify_user_info_add_pair(room_info, _("Authorize"), utf8_value);
+	g_free(utf8_value);
+
+	utf8_value = g_strdup_printf(("%d"), rmd->ext_id);
+	purple_notify_userinfo(gc, utf8_value, room_info, NULL, NULL);
+	g_free(utf8_value);
+
+	purple_notify_user_info_destroy(room_info);
+}
+
+void qq_process_room_cmd_get_info(guint8 *data, gint data_len, guint32 action, PurpleConnection *gc)
+{
 	qq_data *qd;
-	PurpleConversation *purple_conv;
+	qq_room_data *rmd;
+	qq_buddy_data *bd;
+	PurpleChat *chat;
+	PurpleConversation *conv;
 	guint8 organization, role;
 	guint16 unknown, max_members;
 	guint32 member_uid, id, ext_id;
-	GSList *pending_id;
 	guint32 unknown4;
 	guint8 unknown1;
 	gint bytes, num;
 	gchar *notice;
+	gchar *topic_utf8;
 
 	g_return_if_fail(data != NULL && data_len > 0);
 	qd = (qq_data *) gc->proto_data;
 
+	/* qq_show_packet("Room Info", data, data_len); */
+
 	bytes = 0;
 	bytes += qq_get32(&id, data + bytes);
 	g_return_if_fail(id > 0);
@@ -126,22 +195,18 @@
 	bytes += qq_get32(&ext_id, data + bytes);
 	g_return_if_fail(ext_id > 0);
 
-	pending_id = qq_get_pending_id(qd->adding_groups_from_server, id);
-	if (pending_id != NULL) {
-		qq_set_pending_id(&qd->adding_groups_from_server, id, FALSE);
-		qq_group_create_internal_record(gc, id, ext_id, NULL);
-	}
+	chat = qq_room_find_or_new(gc, id, ext_id);
+	g_return_if_fail(chat != NULL);
+	rmd = qq_room_data_find(gc, id);
+	g_return_if_fail(rmd != NULL);
 
-	group = qq_room_search_id(gc, id);
-	g_return_if_fail(group != NULL);
-
-	bytes += qq_get8(&(group->type8), data + bytes);
+	bytes += qq_get8(&(rmd->type8), data + bytes);
 	bytes += qq_get32(&unknown4, data + bytes);	/* unknown 4 bytes */
-	bytes += qq_get32(&(group->creator_uid), data + bytes);
-	bytes += qq_get8(&(group->auth_type), data + bytes);
+	bytes += qq_get32(&(rmd->creator_uid), data + bytes);
+	bytes += qq_get8(&(rmd->auth_type), data + bytes);
 	bytes += qq_get32(&unknown4, data + bytes);	/* oldCategory */
 	bytes += qq_get16(&unknown, data + bytes);
-	bytes += qq_get32(&(group->category), data + bytes);
+	bytes += qq_get32(&(rmd->category), data + bytes);
 	bytes += qq_get16(&max_members, data + bytes);
 	bytes += qq_get8(&unknown1, data + bytes);
 	/* the following, while Eva:
@@ -150,16 +215,21 @@
 	 * qunDestLen(qunDestcontent)) */
 	bytes += qq_get8(&unknown1, data + bytes);
 	purple_debug_info("QQ", "type=%u creatorid=%u category=%u maxmembers=%u\n",
-			group->type8, group->creator_uid, group->category, max_members);
+			rmd->type8, rmd->creator_uid, rmd->category, max_members);
 
+	if (qd->client_version >= 2007) {
+		/* skip 7 bytes unknow in qq2007 0x(00 00 01 00 00 00 fc)*/
+		bytes += 7;
+	}
+	/* qq_show_packet("Room Info", data + bytes, data_len - bytes); */
 	/* strlen + <str content> */
-	bytes += convert_as_pascal_string(data + bytes, &(group->title_utf8), QQ_CHARSET_DEFAULT);
+	bytes += qq_get_vstr(&(rmd->title_utf8), QQ_CHARSET_DEFAULT, data + bytes);
 	bytes += qq_get16(&unknown, data + bytes);	/* 0x0000 */
-	bytes += convert_as_pascal_string(data + bytes, &notice, QQ_CHARSET_DEFAULT);
-	bytes += convert_as_pascal_string(data + bytes, &(group->desc_utf8), QQ_CHARSET_DEFAULT);
+	bytes += qq_get_vstr(&notice, QQ_CHARSET_DEFAULT, data + bytes);
+	bytes += qq_get_vstr(&(rmd->desc_utf8), QQ_CHARSET_DEFAULT, data + bytes);
 
 	purple_debug_info("QQ", "room [%s] notice [%s] desc [%s] unknow 0x%04X\n",
-			group->title_utf8, notice, group->desc_utf8, unknown);
+			rmd->title_utf8, notice, rmd->desc_utf8, unknown);
 
 	num = 0;
 	/* now comes the member list separated by 0x00 */
@@ -175,37 +245,42 @@
 		}
 #endif
 
-		member = qq_group_find_or_add_member(gc, group, member_uid);
-		if (member != NULL)
-			member->role = role;
+		bd = qq_room_buddy_find_or_new(gc, rmd, member_uid);
+		if (bd != NULL)
+			bd->role = role;
 	}
 	if(bytes > data_len) {
 		purple_debug_error("QQ",
 			"group_cmd_get_group_info: Dangerous error! maybe protocol changed, notify me!");
 	}
 
-	purple_debug_info("QQ", "group \"%s\" has %d members\n", group->title_utf8, num);
+	purple_debug_info("QQ", "group \"%s\" has %d members\n", rmd->title_utf8, num);
 
-	if (group->creator_uid == qd->uid)
-		group->my_role = QQ_ROOM_ROLE_ADMIN;
+	if (rmd->creator_uid == qd->uid)
+		rmd->my_role = QQ_ROOM_ROLE_ADMIN;
 
 	/* filter \r\n in notice */
 	qq_filter_str(notice);
-	group->notice_utf8 = strdup(notice);
+	rmd->notice_utf8 = strdup(notice);
 	g_free(notice);
 
-	qq_group_refresh(gc, group);
+	qq_room_update_chat_info(chat, rmd);
 
-	purple_conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
-			group->title_utf8, purple_connection_get_account(gc));
-	if(NULL == purple_conv) {
-		purple_debug_warning("QQ",
-				"Conversation \"%s\" is not open, do not set topic\n", group->title_utf8);
+	if (action == QQ_ROOM_INFO_DISPLAY) {
+		room_info_display(gc, rmd);
+	}
+
+	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+			rmd->title_utf8, purple_connection_get_account(gc));
+	if(NULL == conv) {
+		purple_debug_warning("QQ", "Conversation \"%s\" is not opened\n", rmd->title_utf8);
 		return;
 	}
 
-	purple_debug_info("QQ", "Set chat topic to %s\n", group->notice_utf8);
-	purple_conv_chat_set_topic(PURPLE_CONV_CHAT(purple_conv), NULL, group->notice_utf8);
+	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);
+	purple_conv_chat_set_topic(PURPLE_CONV_CHAT(conv), NULL, topic_utf8);
+	g_free(topic_utf8);
 }
 
 void qq_process_room_cmd_get_onlines(guint8 *data, gint len, PurpleConnection *gc)
@@ -213,8 +288,8 @@
 	guint32 id, member_uid;
 	guint8 unknown;
 	gint bytes, num;
-	qq_group *group;
-	qq_buddy *member;
+	qq_room_data *rmd;
+	qq_buddy_data *bd;
 
 	g_return_if_fail(data != NULL && len > 0);
 
@@ -228,28 +303,29 @@
 	bytes += qq_get8(&unknown, data + bytes);	/* 0x3c ?? */
 	g_return_if_fail(id > 0);
 
-	group = qq_room_search_id(gc, id);
-	if (group == NULL) {
+	rmd = qq_room_data_find(gc, id);
+	if (rmd == NULL) {
 		purple_debug_error("QQ", "We have no group info for internal id [%d]\n", id);
 		return;
 	}
 
 	/* set all offline first, then update those online */
-	set_all_offline(group);
+	set_all_offline(rmd);
 	num = 0;
 	while (bytes < len) {
 		bytes += qq_get32(&member_uid, data + bytes);
 		num++;
-		member = qq_group_find_or_add_member(gc, group, member_uid);
-		if (member != NULL)
-			member->status = QQ_BUDDY_ONLINE_NORMAL;
+		bd = qq_room_buddy_find_or_new(gc, rmd, member_uid);
+		if (bd != NULL)
+			bd->status = QQ_BUDDY_ONLINE_NORMAL;
 	}
 	if(bytes > len) {
 		purple_debug_error("QQ",
 			"group_cmd_get_online_members: Dangerous error! maybe protocol changed, notify developers!");
 	}
 
-	purple_debug_info("QQ", "Group \"%s\" has %d online members\n", group->title_utf8, num);
+	purple_debug_info("QQ", "Group \"%s\" has %d online members\n", rmd->title_utf8, num);
+	qq_room_conv_set_onlines(gc, rmd);
 }
 
 /* process the reply to get_members_info packet */
@@ -259,57 +335,58 @@
 	gint num;
 	guint32 id, member_uid;
 	guint16 unknown;
-	qq_group *group;
-	qq_buddy *member;
+	qq_room_data *rmd;
+	qq_buddy_data *bd;
 	gchar *nick;
 
 	g_return_if_fail(data != NULL && len > 0);
 
-#if 0
-	qq_show_packet("qq_process_room_cmd_get_buddies", data, len);
-#endif
+	/* qq_show_packet("qq_process_room_cmd_get_buddies", data, len); */
 
 	bytes = 0;
 	bytes += qq_get32(&id, data + bytes);
 	g_return_if_fail(id > 0);
 
-	group = qq_room_search_id(gc, id);
-	g_return_if_fail(group != NULL);
+	rmd = qq_room_data_find(gc, id);
+	g_return_if_fail(rmd != NULL);
 
 	num = 0;
 	/* now starts the member info, as get buddy list reply */
 	while (bytes < len) {
 		bytes += qq_get32(&member_uid, data + bytes);
 		g_return_if_fail(member_uid > 0);
-		member = qq_group_find_member_by_uid(group, member_uid);
-		g_return_if_fail(member != NULL);
+		bd = qq_room_buddy_find_or_new(gc, rmd, member_uid);
+		g_return_if_fail(bd != NULL);
 
 		num++;
-		bytes += qq_get16(&(member->face), data + bytes);
-		bytes += qq_get8(&(member->age), data + bytes);
-		bytes += qq_get8(&(member->gender), data + bytes);
-		bytes += convert_as_pascal_string(data + bytes, &nick, QQ_CHARSET_DEFAULT);
+		bytes += qq_get16(&(bd->face), data + bytes);
+		bytes += qq_get8(&(bd->age), data + bytes);
+		bytes += qq_get8(&(bd->gender), data + bytes);
+		bytes += qq_get_vstr(&nick, QQ_CHARSET_DEFAULT, data + bytes);
 		bytes += qq_get16(&unknown, data + bytes);
-		bytes += qq_get8(&(member->ext_flag), data + bytes);
-		bytes += qq_get8(&(member->comm_flag), data + bytes);
+		bytes += qq_get8(&(bd->ext_flag), data + bytes);
+		bytes += qq_get8(&(bd->comm_flag), data + bytes);
 
 		/* filter \r\n in nick */
 		qq_filter_str(nick);
-		member->nickname = g_strdup(nick);
+		bd->nickname = g_strdup(nick);
 		g_free(nick);
 
 #if 0
 		purple_debug_info("QQ",
 				"member [%09d]: ext_flag=0x%02x, comm_flag=0x%02x, nick=%s\n",
-				member_uid, member->ext_flag, member->comm_flag, member->nickname);
+				member_uid, bd->ext_flag, bd->comm_flag, bd->nickname);
 #endif
 
-		member->last_update = time(NULL);
+		bd->last_update = time(NULL);
 	}
 	if (bytes > len) {
 		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", group->title_utf8, num);
+	purple_debug_info("QQ", "Group \"%s\" obtained %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_info.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/group_info.h	Thu Nov 06 03:20:05 2008 +0000
@@ -29,9 +29,14 @@
 #include "connection.h"
 #include "group.h"
 
-gint qq_request_room_get_buddies(PurpleConnection *gc, qq_group *group, gint update_class);
+enum {
+	QQ_ROOM_INFO_UPDATE_ONLY = 0,
+	QQ_ROOM_INFO_DISPLAY,
+};
 
-void qq_process_room_cmd_get_info(guint8 *data, gint len, PurpleConnection *gc);
+gint qq_request_room_get_buddies(PurpleConnection *gc, guint32 room_id, gint update_class);
+
+void qq_process_room_cmd_get_info(guint8 *data, gint len, guint32 action, PurpleConnection *gc);
 void qq_process_room_cmd_get_onlines(guint8 *data, gint len, PurpleConnection *gc);
 void qq_process_room_cmd_get_buddies(guint8 *data, gint len, PurpleConnection *gc);
 #endif
--- a/libpurple/protocols/qq/group_internal.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/group_internal.c	Thu Nov 06 03:20:05 2008 +0000
@@ -27,216 +27,396 @@
 #include "debug.h"
 
 #include "buddy_opt.h"
-#include "group_free.h"
 #include "group_internal.h"
 #include "utils.h"
 
-static gchar *get_role_desc(qq_group *group)
+static qq_room_data *room_data_new(guint32 id, guint32 ext_id, gchar *title)
 {
-	const char *role_desc;
-	g_return_val_if_fail(group != NULL, g_strdup(""));
+	qq_room_data *rmd;
+
+	purple_debug_info("QQ", "Created room data: %s, ext id %d, id %d\n",
+			title, ext_id, id);
+	rmd = g_new0(qq_room_data, 1);
+	rmd->my_role = QQ_ROOM_ROLE_NO;
+	rmd->id = id;
+	rmd->ext_id = ext_id;
+	rmd->type8 = 0x01;       /* assume permanent Qun */
+	rmd->creator_uid = 10000;     /* assume by QQ admin */
+	rmd->category = 0x01;
+	rmd->auth_type = 0x02;        /* assume need auth */
+	rmd->title_utf8 = g_strdup(title == NULL ? "" : title);
+	rmd->desc_utf8 = g_strdup("");
+	rmd->notice_utf8 = g_strdup("");
+	rmd->members = NULL;
+	rmd->is_got_buddies = FALSE;
+	return rmd;
+}
 
-	switch (group->my_role) {
-	case QQ_ROOM_ROLE_NO:
-		role_desc = _("I am not a member");
-		break;
-	case QQ_ROOM_ROLE_YES:
-		role_desc = _("I am a member");
-		break;
-	case QQ_ROOM_ROLE_REQUESTING:
-		role_desc = _("I am requesting");
-		break;
-	case QQ_ROOM_ROLE_ADMIN:
-		role_desc = _("I am the admin");
-		break;
-	default:
-		role_desc = _("Unknown status");
+/* create a qq_room_data from hashtable */
+static qq_room_data *room_data_new_by_hashtable(PurpleConnection *gc, GHashTable *data)
+{
+	qq_room_data *rmd;
+	guint32 id, ext_id;
+	gchar *value;
+
+	value = g_hash_table_lookup(data, QQ_ROOM_KEY_INTERNAL_ID);
+	id = value ? strtol(value, NULL, 10) : 0;
+	value= g_hash_table_lookup(data, QQ_ROOM_KEY_EXTERNAL_ID);
+	ext_id = value ? strtol(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);
+	rmd->my_role = QQ_ROOM_ROLE_YES;
+	return rmd;
+}
+
+/* gracefully free all members in a room */
+static void room_buddies_free(qq_room_data *rmd)
+{
+	gint i;
+	GList *list;
+	qq_buddy_data *bd;
+
+	g_return_if_fail(rmd != NULL);
+	i = 0;
+	while (NULL != (list = rmd->members)) {
+		bd = (qq_buddy_data *) list->data;
+		i++;
+		rmd->members = g_list_remove(rmd->members, bd);
+		qq_buddy_data_free(bd);
 	}
 
-	return g_strdup(role_desc);
+	rmd->members = NULL;
+}
+
+/* gracefully free the memory for one qq_room_data */
+static void room_data_free(qq_room_data *rmd)
+{
+	g_return_if_fail(rmd != NULL);
+	room_buddies_free(rmd);
+	g_free(rmd->title_utf8);
+	g_free(rmd->desc_utf8);
+	g_free(rmd->notice_utf8);
+	g_free(rmd);
 }
 
-static void add_room_to_blist(PurpleConnection *gc, qq_group *group)
+void qq_room_update_chat_info(PurpleChat *chat, qq_room_data *rmd)
+{
+	if (rmd->title_utf8 != NULL && strlen(rmd->title_utf8) > 0) {
+		purple_blist_alias_chat(chat, rmd->title_utf8);
+	}
+	g_hash_table_replace(chat->components,
+		     g_strdup(QQ_ROOM_KEY_INTERNAL_ID),
+		     g_strdup_printf("%d", rmd->id));
+	g_hash_table_replace(chat->components,
+		     g_strdup(QQ_ROOM_KEY_EXTERNAL_ID),
+		     g_strdup_printf("%d", rmd->ext_id));
+	g_hash_table_replace(chat->components,
+		     g_strdup(QQ_ROOM_KEY_TITLE_UTF8), g_strdup(rmd->title_utf8));
+}
+
+static PurpleChat *chat_new(PurpleConnection *gc, qq_room_data *rmd)
 {
 	GHashTable *components;
 	PurpleGroup *g;
 	PurpleChat *chat;
-	components = qq_group_to_hashtable(group);
-	chat = purple_chat_new(purple_connection_get_account(gc), group->title_utf8, components);
-	g = qq_get_purple_group(PURPLE_GROUP_QQ_QUN);
+
+	purple_debug_info("QQ", "Add new chat: id %d, ext id %d, title %s\n",
+		rmd->id, rmd->ext_id, 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_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_EXTERNAL_ID),
+			    g_strdup_printf("%d", 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);
+	g = qq_group_find_or_new(PURPLE_GROUP_QQ_QUN);
 	purple_blist_add_chat(chat, g, NULL);
-	purple_debug_info("QQ", "You have added group \"%s\" to blist locally\n", group->title_utf8);
+
+	return chat;
+}
+
+PurpleChat *qq_room_find_or_new(PurpleConnection *gc, guint32 id, guint32 ext_id)
+{
+	qq_data *qd;
+	qq_room_data *rmd;
+	PurpleChat *chat;
+	gchar *num_str;
+
+	g_return_val_if_fail (gc != NULL && gc->proto_data != NULL, NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	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);
+
+	rmd = qq_room_data_find(gc, id);
+	if (rmd == NULL) {
+		rmd = room_data_new(id, ext_id, NULL);
+		g_return_val_if_fail(rmd != NULL, NULL);
+		rmd->my_role = QQ_ROOM_ROLE_YES;
+		qd->groups = g_list_append(qd->groups, rmd);
+	}
+
+	num_str = g_strdup_printf("%d", ext_id);
+	chat = purple_blist_find_chat(purple_connection_get_account(gc), num_str);
+	g_free(num_str);
+	if (chat) {
+		return chat;
+	}
+
+	return chat_new(gc, rmd);
 }
 
-/* Create a dummy qq_group, which includes only internal_id, ext_id,
- * and potentially title_utf8, in case we need to call group_conv_show_window
- * right after creation. All other attributes are set to empty.
- * We need to send a get_group_info to the QQ server to update it right away */
-qq_group *qq_group_create_internal_record(PurpleConnection *gc,
-                guint32 internal_id, guint32 ext_id, gchar *title_utf8)
+void qq_room_remove(PurpleConnection *gc, guint32 id)
 {
-        qq_group *group;
-        qq_data *qd;
+	qq_data *qd;
+	PurpleChat *chat;
+	qq_room_data *rmd;
+	gchar *num_str;
+	guint32 ext_id;
 
-        g_return_val_if_fail(internal_id > 0, NULL);
-        qd = (qq_data *) gc->proto_data;
+	g_return_if_fail (gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
 
-        group = g_new0(qq_group, 1);
-        group->my_role = QQ_ROOM_ROLE_NO;
-        group->my_role_desc = get_role_desc(group);
-        group->id = internal_id;
-        group->ext_id = ext_id;
-        group->type8 = 0x01;       /* assume permanent Qun */
-        group->creator_uid = 10000;     /* assume by QQ admin */
-        group->category = 0x01;
-        group->auth_type = 0x02;        /* assume need auth */
-        group->title_utf8 = g_strdup(title_utf8 == NULL ? "" : title_utf8);
-        group->desc_utf8 = g_strdup("");
-        group->notice_utf8 = g_strdup("");
-        group->members = NULL;
+	purple_debug_info("QQ", "Find and remove room data, id %d", id);
+	rmd = qq_room_data_find(gc, id);
+	g_return_if_fail (rmd != NULL);
+
+	ext_id = rmd->ext_id;
+	qd->groups = g_list_remove(qd->groups, rmd);
+	room_data_free(rmd);
 
-        qd->groups = g_list_append(qd->groups, group);
-        add_room_to_blist(gc, group);
+	purple_debug_info("QQ", "Find and remove chat, ext_id %d", ext_id);
+	num_str = g_strdup_printf("%d", ext_id);
+	chat = purple_blist_find_chat(purple_connection_get_account(gc), num_str);
+	g_free(num_str);
 
-        return group;
+	g_return_if_fail (chat != NULL);
+
+	purple_blist_remove_chat(chat);
 }
 
-void qq_group_delete_internal_record(qq_data *qd, guint32 id)
+/* find a qq_buddy_data by uid, called by im.c */
+qq_buddy_data *qq_room_buddy_find(qq_room_data *rmd, guint32 uid)
 {
-        qq_group *group;
-        GList *list;
+	GList *list;
+	qq_buddy_data *bd;
+	g_return_val_if_fail(rmd != NULL && uid > 0, NULL);
+
+	list = rmd->members;
+	while (list != NULL) {
+		bd = (qq_buddy_data *) list->data;
+		if (bd->uid == uid)
+			return bd;
+		else
+			list = list->next;
+	}
 
-        list = qd->groups;
-        while (list != NULL) {
-                group = (qq_group *) qd->groups->data;
-                if (id == group->id) {
-                        qd->groups = g_list_remove(qd->groups, group);
-                        qq_group_free(group);
-                        break;
-                } else {
-                        list = list->next;
-                }
-        }
+	return NULL;
+}
+
+/* remove a qq_buddy_data by uid, called by qq_group_opt.c */
+void qq_room_buddy_remove(qq_room_data *rmd, guint32 uid)
+{
+	GList *list;
+	qq_buddy_data *bd;
+	g_return_if_fail(rmd != NULL && uid > 0);
+
+	list = rmd->members;
+	while (list != NULL) {
+		bd = (qq_buddy_data *) list->data;
+		if (bd->uid == uid) {
+			rmd->members = g_list_remove(rmd->members, bd);
+			return;
+		} else {
+			list = list->next;
+		}
+	}
 }
 
-/* convert a qq_group to hash-table, which could be component of PurpleChat */
-GHashTable *qq_group_to_hashtable(qq_group *group)
+qq_buddy_data *qq_room_buddy_find_or_new(PurpleConnection *gc, qq_room_data *rmd, guint32 member_uid)
 {
-	GHashTable *components;
-	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_ROLE), g_strdup_printf("%d", group->my_role));
-	group->my_role_desc = get_role_desc(group);
+	qq_buddy_data *member, *bd;
+	PurpleBuddy *buddy;
+	g_return_val_if_fail(rmd != NULL && member_uid > 0, NULL);
 
-	g_hash_table_insert(components,
-			    g_strdup(QQ_ROOM_KEY_INTERNAL_ID), g_strdup_printf("%d", group->id));
-	g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_EXTERNAL_ID),
-			    g_strdup_printf("%d", group->ext_id));
-	g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_TYPE), g_strdup_printf("%d", group->type8));
-	g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_CREATOR_UID), g_strdup_printf("%d", group->creator_uid));
-	g_hash_table_insert(components,
-			    g_strdup(QQ_ROOM_KEY_CATEGORY), g_strdup_printf("%d", group->category));
-	g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_AUTH_TYPE), g_strdup_printf("%d", group->auth_type));
-	g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_ROLE_DESC), g_strdup(group->my_role_desc));
-	g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_TITLE_UTF8), g_strdup(group->title_utf8));
-	g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_DESC_UTF8), g_strdup(group->desc_utf8));
-	return components;
+	member = qq_room_buddy_find(rmd, member_uid);
+	if (member == NULL) {	/* first appear during my session */
+		member = g_new0(qq_buddy_data, 1);
+		member->uid = member_uid;
+		buddy = purple_find_buddy(purple_connection_get_account(gc), uid_to_purple_name(member_uid));
+		if (buddy != NULL) {
+			bd = (qq_buddy_data *) buddy->proto_data;
+			if (bd != NULL && bd->nickname != NULL)
+				member->nickname = g_strdup(bd->nickname);
+			else if (buddy->alias != NULL)
+				member->nickname = g_strdup(buddy->alias);
+		}
+		rmd->members = g_list_append(rmd->members, member);
+	}
+
+	return member;
 }
 
-/* create a qq_group from hashtable */
-qq_group *qq_room_create_by_hashtable(PurpleConnection *gc, GHashTable *data)
+qq_room_data *qq_room_data_find(PurpleConnection *gc, guint32 room_id)
 {
+	GList *list;
+	qq_room_data *rmd;
 	qq_data *qd;
-	qq_group *group;
+
+	qd = (qq_data *) gc->proto_data;
+
+	if (qd->groups == NULL || room_id <= 0)
+		return 0;
 
-	g_return_val_if_fail(data != NULL, NULL);
+	list = qd->groups;
+	while (list != NULL) {
+		rmd = (qq_room_data *) list->data;
+		if (rmd->id == room_id) {
+			return rmd;
+		}
+		list = list->next;
+	}
+
+	return NULL;
+}
+
+guint32 qq_room_get_next(PurpleConnection *gc, guint32 room_id)
+{
+	GList *list;
+	qq_room_data *rmd;
+	qq_data *qd;
+	gboolean is_find = FALSE;
+
 	qd = (qq_data *) gc->proto_data;
 
-	group = g_new0(qq_group, 1);
-	group->my_role =
-	    qq_string_to_dec_value
-	    (NULL ==
-	     g_hash_table_lookup(data,
-				 QQ_ROOM_KEY_ROLE) ?
-	     g_strdup_printf("%d", QQ_ROOM_ROLE_NO) :
-	     g_hash_table_lookup(data, QQ_ROOM_KEY_ROLE));
-	group->id = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_ROOM_KEY_INTERNAL_ID));
-	group->ext_id = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_ROOM_KEY_EXTERNAL_ID));
-	group->type8 = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_ROOM_KEY_TYPE));
-	group->creator_uid = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_ROOM_KEY_CREATOR_UID));
-	group->category = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_ROOM_KEY_CATEGORY));
-	group->auth_type = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_ROOM_KEY_AUTH_TYPE));
-	group->title_utf8 = g_strdup(g_hash_table_lookup(data, QQ_ROOM_KEY_TITLE_UTF8));
-	group->desc_utf8 = g_strdup(g_hash_table_lookup(data, QQ_ROOM_KEY_DESC_UTF8));
-	group->my_role_desc = get_role_desc(group);
-	group->is_got_info = FALSE;
+	if (qd->groups == NULL) {
+		return 0;
+	}
+
+	 if (room_id <= 0) {
+	 	rmd = (qq_room_data *) qd->groups->data;
+		return rmd->id;
+	}
 
-	qd->groups = g_list_append(qd->groups, group);
-	return group;
+	list = qd->groups;
+	while (list != NULL) {
+		rmd = (qq_room_data *) list->data;
+		list = list->next;
+		if (rmd->id == room_id) {
+			is_find = TRUE;
+			break;
+		}
+	}
+
+	g_return_val_if_fail(is_find, 0);
+	if (list == NULL) return 0;	/* be the end */
+ 	rmd = (qq_room_data *) list->data;
+	g_return_val_if_fail(rmd != NULL, 0);
+	return rmd->id;
 }
 
-/* refresh group local subscription */
-void qq_group_refresh(PurpleConnection *gc, qq_group *group)
+guint32 qq_room_get_next_conv(PurpleConnection *gc, guint32 room_id)
 {
+	GList *list;
+	qq_room_data *rmd;
+	qq_data *qd;
+	gboolean is_find;
+
+	qd = (qq_data *) gc->proto_data;
+
+ 	list = qd->groups;
+	if (room_id > 0) {
+		/* search next room */
+		is_find = FALSE;
+		while (list != NULL) {
+			rmd = (qq_room_data *) list->data;
+			list = list->next;
+			if (rmd->id == room_id) {
+				is_find = TRUE;
+				break;
+			}
+		}
+		g_return_val_if_fail(is_find, 0);
+	}
+
+	while (list != NULL) {
+		rmd = (qq_room_data *) list->data;
+		g_return_val_if_fail(rmd != NULL, 0);
+
+		if (rmd->my_role == QQ_ROOM_ROLE_YES || rmd->my_role == QQ_ROOM_ROLE_ADMIN) {
+			if (NULL != purple_find_conversation_with_account(
+						PURPLE_CONV_TYPE_CHAT,rmd->title_utf8, purple_connection_get_account(gc))) {
+				/* In convseration*/
+				return rmd->id;
+			}
+		}
+		list = list->next;
+	}
+
+	return 0;
+}
+
+/* this should be called upon signin, even when we did not open group chat window */
+void qq_room_data_initial(PurpleConnection *gc)
+{
+	PurpleAccount *account;
 	PurpleChat *chat;
-	gchar *ext_id;
-	g_return_if_fail(group != NULL);
+	PurpleGroup *purple_group;
+	PurpleBlistNode *node;
+	qq_data *qd;
+	qq_room_data *rmd;
+	gint count;
 
-	ext_id = g_strdup_printf("%d", group->ext_id);
-	chat = purple_blist_find_chat(purple_connection_get_account(gc), ext_id);
-	g_free(ext_id);
-	if (chat == NULL && group->my_role != QQ_ROOM_ROLE_NO) {
-		add_room_to_blist(gc, group);
+	account = purple_connection_get_account(gc);
+	qd = (qq_data *) gc->proto_data;
+
+	purple_debug_info("QQ", "Initial QQ Qun configurations\n");
+	purple_group = purple_find_group(PURPLE_GROUP_QQ_QUN);
+	if (purple_group == NULL) {
+		purple_debug_info("QQ", "We have no QQ Qun\n");
 		return;
 	}
 
-	if (chat == NULL) {
-		return;
+	count = 0;
+	for (node = ((PurpleBlistNode *) purple_group)->child; node != NULL; node = node->next) {
+		if ( !PURPLE_BLIST_NODE_IS_CHAT(node)) {
+			continue;
+		}
+		/* got one */
+		chat = (PurpleChat *) node;
+		if (account != chat->account)	/* not qq account*/
+			continue;
+
+		rmd = room_data_new_by_hashtable(gc, chat->components);
+		qd->groups = g_list_append(qd->groups, rmd);
+		count++;
 	}
 
-	/* we have a local record, update its info */
-	/* if there is title_utf8, we update the group name */
-	if (group->title_utf8 != NULL && strlen(group->title_utf8) > 0)
-		purple_blist_alias_chat(chat, group->title_utf8);
-	g_hash_table_replace(chat->components,
-		     g_strdup(QQ_ROOM_KEY_ROLE), g_strdup_printf("%d", group->my_role));
-	group->my_role_desc = get_role_desc(group);
-	g_hash_table_replace(chat->components,
-		     g_strdup(QQ_ROOM_KEY_ROLE_DESC), g_strdup(group->my_role_desc));
-	g_hash_table_replace(chat->components,
-		     g_strdup(QQ_ROOM_KEY_INTERNAL_ID),
-		     g_strdup_printf("%d", group->id));
-	g_hash_table_replace(chat->components,
-		     g_strdup(QQ_ROOM_KEY_EXTERNAL_ID),
-		     g_strdup_printf("%d", group->ext_id));
-	g_hash_table_replace(chat->components,
-		     g_strdup(QQ_ROOM_KEY_TYPE), g_strdup_printf("%d", group->type8));
-	g_hash_table_replace(chat->components,
-		     g_strdup(QQ_ROOM_KEY_CREATOR_UID), g_strdup_printf("%d", group->creator_uid));
-	g_hash_table_replace(chat->components,
-		     g_strdup(QQ_ROOM_KEY_CATEGORY),
-		     g_strdup_printf("%d", group->category));
-	g_hash_table_replace(chat->components,
-		     g_strdup(QQ_ROOM_KEY_AUTH_TYPE), g_strdup_printf("%d", group->auth_type));
-	g_hash_table_replace(chat->components,
-		     g_strdup(QQ_ROOM_KEY_TITLE_UTF8), g_strdup(group->title_utf8));
-	g_hash_table_replace(chat->components,
-		     g_strdup(QQ_ROOM_KEY_DESC_UTF8), g_strdup(group->desc_utf8));
+	purple_debug_info("QQ", "Load %d QQ Qun configurations\n", count);
 }
 
-/* NOTE: If we knew how to convert between an external and internal group id, as the official
- * client seems to, the following would be unnecessary. That would be ideal. */
-
-/* Use list to specify if id's alternate id is pending discovery. */
-void qq_set_pending_id(GSList **list, guint32 id, gboolean pending)
+void qq_room_data_free_all(PurpleConnection *gc)
 {
-	if (pending)
-		*list = g_slist_prepend(*list, GINT_TO_POINTER(id));
-	else
-		*list = g_slist_remove(*list, GINT_TO_POINTER(id));
-}
+	qq_data *qd;
+	qq_room_data *rmd;
+	gint count;
+
+	g_return_if_fail (gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
 
-/* Return the location of id in list, or NULL if not found */
-GSList *qq_get_pending_id(GSList *list, guint32 id)
-{
-        return g_slist_find(list, GINT_TO_POINTER(id));
+	count = 0;
+	while (qd->groups != NULL) {
+		rmd = (qq_room_data *) qd->groups->data;
+		qd->groups = g_list_remove(qd->groups, rmd);
+		room_data_free(rmd);
+		count++;
+	}
+
+	if (count > 0) {
+		purple_debug_info("QQ", "%d rooms are freed\n", count);
+	}
 }
--- a/libpurple/protocols/qq/group_internal.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/group_internal.h	Thu Nov 06 03:20:05 2008 +0000
@@ -28,27 +28,23 @@
 #include <glib.h>
 #include "group.h"
 
-#define QQ_ROOM_KEY_ROLE									"my_role"
-#define QQ_ROOM_KEY_ROLE_DESC						"my_role_desc"
 #define QQ_ROOM_KEY_INTERNAL_ID					"id"
 #define QQ_ROOM_KEY_EXTERNAL_ID					"ext_id"
-#define QQ_ROOM_KEY_TYPE									"type"
-#define QQ_ROOM_KEY_CREATOR_UID					"creator_uid"
-#define QQ_ROOM_KEY_CATEGORY							"category"
-#define QQ_ROOM_KEY_AUTH_TYPE						"auth_type"
-#define QQ_ROOM_KEY_TITLE_UTF8						"title_utf8"
-#define QQ_ROOM_KEY_DESC_UTF8						"desc_utf8"
+#define QQ_ROOM_KEY_TITLE_UTF8					"title_utf8"
+
+PurpleChat *qq_room_find_or_new(PurpleConnection *gc, guint32 id, guint32 ext_id);
+void qq_room_remove(PurpleConnection *gc, guint32 id);
+void qq_room_update_chat_info(PurpleChat *chat, qq_room_data *rmd);
 
-qq_group *qq_group_create_internal_record(PurpleConnection *gc,
-		guint32 internal_id, guint32 ext_id, gchar *group_name_utf8);
-void qq_group_delete_internal_record(qq_data *qd, guint32 id);
+qq_buddy_data *qq_room_buddy_find(qq_room_data *rmd, guint32 uid);
+void qq_room_buddy_remove(qq_room_data *rmd, guint32 uid);
+qq_buddy_data *qq_room_buddy_find_or_new(PurpleConnection *gc, qq_room_data *rmd, guint32 member_uid);
 
-GHashTable *qq_group_to_hashtable(qq_group *group);
-qq_group *qq_room_create_by_hashtable(PurpleConnection *gc, GHashTable *data);
+void qq_room_data_initial(PurpleConnection *gc);
+void qq_room_data_free_all(PurpleConnection *gc);
+qq_room_data *qq_room_data_find(PurpleConnection *gc, guint32 room_id);
 
-void qq_group_refresh(PurpleConnection *gc, qq_group *group);
-
-void qq_set_pending_id(GSList **list, guint32 id, gboolean pending);
-GSList *qq_get_pending_id(GSList *list, guint32 id);
+guint32 qq_room_get_next(PurpleConnection *gc, guint32 room_id);
+guint32 qq_room_get_next_conv(PurpleConnection *gc, guint32 room_id);
 
 #endif
--- a/libpurple/protocols/qq/group_join.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/group_join.c	Thu Nov 06 03:20:05 2008 +0000
@@ -29,18 +29,14 @@
 #include "request.h"
 #include "server.h"
 
-#include "buddy_opt.h"
 #include "char_conv.h"
-#include "group_conv.h"
-#include "group_find.h"
-#include "group_free.h"
+#include "im.h"
 #include "group_internal.h"
 #include "group_info.h"
 #include "group_join.h"
 #include "group_opt.h"
-#include "group_conv.h"
-#include "group_search.h"
-#include "header_info.h"
+#include "group_im.h"
+#include "qq_define.h"
 #include "packet_parse.h"
 #include "qq_network.h"
 #include "qq_process.h"
@@ -51,129 +47,140 @@
 	QQ_ROOM_JOIN_DENIED = 0x03,
 };
 
-static void _qq_group_exit_with_gc_and_id(gc_and_uid *g)
+enum {
+	QQ_ROOM_SEARCH_TYPE_BY_ID = 0x01,
+	QQ_ROOM_SEARCH_TYPE_DEMO = 0x02
+};
+
+static void group_quit_cb(qq_room_req *add_req)
 {
 	PurpleConnection *gc;
 	guint32 id;
-	qq_group *group;
+	qq_room_data *rmd;
 
-	gc = g->gc;
-	id = g->uid;
+	if (add_req->gc == NULL || add_req->id == 0) {
+		g_free(add_req);
+		return;
+	}
 
-	group = qq_room_search_id(gc, id);
-	g_return_if_fail(group != NULL);
+	gc = add_req->gc;
+	id = add_req->id;
 
-	qq_send_room_cmd_only(gc, QQ_ROOM_CMD_QUIT, group->id);
+	rmd = qq_room_data_find(gc, id);
+	if (rmd == NULL) {
+		g_free(add_req);
+		return;
+	}
+
+	qq_send_room_cmd_only(gc, QQ_ROOM_CMD_QUIT, rmd->id);
+	g_free(add_req);
 }
 
 /* send packet to join a group without auth */
-void qq_request_room_join(PurpleConnection *gc, qq_group *group)
+void qq_request_room_join(PurpleConnection *gc, qq_room_data *rmd)
 {
-	g_return_if_fail(group != NULL);
+	g_return_if_fail(rmd != NULL);
 
-	if (group->my_role == QQ_ROOM_ROLE_NO) {
-		group->my_role = QQ_ROOM_ROLE_REQUESTING;
-		qq_group_refresh(gc, group);
+	if (rmd->my_role == QQ_ROOM_ROLE_NO) {
+		rmd->my_role = QQ_ROOM_ROLE_REQUESTING;
 	}
 
-	switch (group->auth_type) {
+	switch (rmd->auth_type) {
 	case QQ_ROOM_AUTH_TYPE_NO_AUTH:
 	case QQ_ROOM_AUTH_TYPE_NEED_AUTH:
 		break;
 	case QQ_ROOM_AUTH_TYPE_NO_ADD:
-		if (group->my_role == QQ_ROOM_ROLE_NO
-				&& group->my_role == QQ_ROOM_ROLE_REQUESTING) {
+		if (rmd->my_role == QQ_ROOM_ROLE_NO
+				&& rmd->my_role == QQ_ROOM_ROLE_REQUESTING) {
 			purple_notify_warning(gc, NULL, _("The Qun does not allow others to join"), NULL);
 			return;
 		}
 		break;
 	default:
-		purple_debug_error("QQ", "Unknown room auth type: %d\n", group->auth_type);
+		purple_debug_error("QQ", "Unknown room auth type: %d\n", rmd->auth_type);
 		break;
 	}
 
-	qq_send_room_cmd_only(gc, QQ_ROOM_CMD_JOIN, group->id);
+	qq_send_room_cmd_only(gc, QQ_ROOM_CMD_JOIN, rmd->id);
 }
 
-static void _qq_group_join_auth_with_gc_and_id(gc_and_uid *g, const gchar *reason_utf8)
+static void group_join_cb(qq_room_req *add_req, const gchar *reason_utf8)
 {
-	PurpleConnection *gc;
-	qq_group *group;
-	guint32 id;
+	qq_room_data *rmd;
+
+	g_return_if_fail(add_req != NULL);
+	if (add_req->gc == NULL || add_req->id == 0) {
+		g_free(add_req);
+		return;
+	}
 
-	gc = g->gc;
-	id = g->uid;
+	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);
+		g_free(add_req);
+		return;
+	}
 
-	group = qq_room_search_id(gc, id);
-	if (group == NULL) {
-		purple_debug_error("QQ", "Can not find qq_group by internal_id: %d\n", id);
-		return;
-	} else {		/* everything is OK */
-		qq_send_cmd_group_auth(gc, group, QQ_ROOM_AUTH_REQUEST_APPLY, 0, reason_utf8);
-	}
+	qq_send_cmd_group_auth(add_req->gc, rmd, QQ_ROOM_AUTH_REQUEST_APPLY, 0, reason_utf8);
+	g_free(add_req);
 }
 
-static void _qq_group_join_auth(PurpleConnection *gc, qq_group *group)
+static void room_join_cancel_cb(qq_room_req *add_req, const gchar *msg)
+{
+	g_return_if_fail(add_req != NULL);
+	g_free(add_req);
+}
+
+static void do_room_join_request(PurpleConnection *gc, qq_room_data *rmd)
 {
 	gchar *msg;
-	gc_and_uid *g;
-	g_return_if_fail(group != NULL);
+	qq_room_req *add_req;
+	g_return_if_fail(rmd != NULL);
 
-	purple_debug_info("QQ", "Group (internal id: %d) needs authentication\n", group->id);
+	purple_debug_info("QQ", "Room (internal id: %d) needs authentication\n", rmd->id);
 
-	msg = g_strdup_printf("Group \"%s\" needs authentication\n", group->title_utf8);
-	g = g_new0(gc_and_uid, 1);
-	g->gc = gc;
-	g->uid = group->id;
-	purple_request_input(gc, NULL, msg,
+	msg = g_strdup_printf("QQ Qun %d needs authentication\n", rmd->ext_id);
+	add_req = g_new0(qq_room_req, 1);
+	add_req->gc = gc;
+	add_req->id = rmd->id;
+	purple_request_input(gc, _("Join QQ Qun"), msg,
 			   _("Input request here"),
 			   _("Would you be my friend?"), TRUE, FALSE, NULL,
 			   _("Send"),
-			   G_CALLBACK(_qq_group_join_auth_with_gc_and_id),
-			   _("Cancel"), G_CALLBACK(qq_do_nothing_with_gc_and_uid),
-			   purple_connection_get_account(gc), group->title_utf8, NULL,
-			   g);
+			   G_CALLBACK(group_join_cb),
+			   _("Cancel"), G_CALLBACK(room_join_cancel_cb),
+			   purple_connection_get_account(gc), rmd->title_utf8, NULL,
+			   add_req);
 	g_free(msg);
 }
 
-void qq_send_cmd_group_auth(PurpleConnection *gc, qq_group *group, guint8 opt, guint32 uid, const gchar *reason_utf8)
+void qq_send_cmd_group_auth(PurpleConnection *gc, qq_room_data *rmd, 
+		guint8 opt, guint32 uid, const gchar *reason_utf8)
 {
-	guint8 *raw_data;
-	gchar *reason_qq;
+	guint8 raw_data[MAX_PACKET_SIZE - 16];
 	gint bytes;
 
-	g_return_if_fail(group != NULL);
-
-	if (reason_utf8 == NULL || strlen(reason_utf8) == 0)
-		reason_qq = g_strdup("");
-	else
-		reason_qq = utf8_to_qq(reason_utf8, QQ_CHARSET_DEFAULT);
+	g_return_if_fail(rmd != NULL);
 
 	if (opt == QQ_ROOM_AUTH_REQUEST_APPLY) {
-		group->my_role = QQ_ROOM_ROLE_REQUESTING;
-		qq_group_refresh(gc, group);
+		rmd->my_role = QQ_ROOM_ROLE_REQUESTING;
 		uid = 0;
 	}
 
-	raw_data = g_newa(guint8, 6 + strlen(reason_qq));
-
 	bytes = 0;
 	bytes += qq_put8(raw_data + bytes, opt);
 	bytes += qq_put32(raw_data + bytes, uid);
-	bytes += qq_put8(raw_data + bytes, strlen(reason_qq));
-	bytes += qq_putdata(raw_data + bytes, (guint8 *) reason_qq, strlen(reason_qq));
+	bytes += qq_put_vstr(raw_data + bytes, reason_utf8, QQ_CHARSET_DEFAULT);
 
-	qq_send_room_cmd(gc, QQ_ROOM_CMD_AUTH, group->id, raw_data, bytes);
+	qq_send_room_cmd(gc, QQ_ROOM_CMD_AUTH, rmd->id, raw_data, bytes);
 }
 
 /* If comes here, cmd is OK already */
 void qq_process_group_cmd_exit_group(guint8 *data, gint len, PurpleConnection *gc)
 {
+	qq_data *qd;
 	gint bytes;
 	guint32 id;
-	PurpleChat *chat;
-	qq_group *group;
-	qq_data *qd;
 
 	g_return_if_fail(data != NULL && len > 0);
 	qd = (qq_data *) gc->proto_data;
@@ -186,15 +193,7 @@
 	bytes = 0;
 	bytes += qq_get32(&id, data + bytes);
 
-	group = qq_room_search_id(gc, id);
-	if (group != NULL) {
-		chat = purple_blist_find_chat
-			    (purple_connection_get_account(gc), g_strdup_printf("%d", group->ext_id));
-		if (chat != NULL)
-			purple_blist_remove_chat(chat);
-		qq_group_delete_internal_record(qd, id);
-	}
-	purple_notify_info(gc, _("QQ Qun Operation"), _("Successed:"), _("Remove from Qun"));
+	qq_room_remove(gc, id);
 }
 
 /* Process the reply to group_auth subcmd */
@@ -203,6 +202,8 @@
 	gint bytes;
 	guint32 id;
 	qq_data *qd;
+	qq_room_data *rmd;
+	gchar *msg;
 
 	g_return_if_fail(data != NULL && len > 0);
 	qd = (qq_data *) gc->proto_data;
@@ -216,7 +217,14 @@
 	bytes += qq_get32(&id, data + bytes);
 	g_return_if_fail(id > 0);
 
-	purple_notify_info(gc, _("QQ Qun Operation"), _("Successed:"), _("Join to Qun"));
+	rmd = qq_room_data_find(gc, id);
+	if (rmd != NULL) {
+		msg = g_strdup_printf(_("Successed join to Qun %s (%d)"), rmd->title_utf8, rmd->ext_id);
+		qq_got_attention(gc, msg);
+		g_free(msg);
+	} else {
+		qq_got_attention(gc, _("Successed join to Qun"));
+	}
 }
 
 /* process group cmd reply "join group" */
@@ -225,14 +233,14 @@
 	gint bytes;
 	guint32 id;
 	guint8 reply;
-	qq_group *group;
+	qq_room_data *rmd;
 	gchar *msg;
 
 	g_return_if_fail(data != NULL && len > 0);
 
 	if (len < 5) {
 		purple_debug_error("QQ",
-			   "Invalid join group reply, expect %d bytes, read %d bytes\n", 5, len);
+			   "Invalid join room reply, expect %d bytes, read %d bytes\n", 5, len);
 		return;
 	}
 
@@ -241,34 +249,32 @@
 	bytes += qq_get8(&reply, data + bytes);
 
 	/* join group OK */
-	group = qq_room_search_id(gc, id);
+	rmd = qq_room_data_find(gc, id);
 	/* need to check if group is NULL or not. */
-	g_return_if_fail(group != NULL);
+	g_return_if_fail(rmd != NULL);
 	switch (reply) {
 	case QQ_ROOM_JOIN_OK:
-		purple_debug_info("QQ", "Successed in joining group \"%s\"\n", group->title_utf8);
-		group->my_role = QQ_ROOM_ROLE_YES;
-		qq_group_refresh(gc, group);
+		purple_debug_info("QQ", "Successed 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_create(gc, group);
+		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",
-			   group->ext_id, group->title_utf8);
-		group->my_role = QQ_ROOM_ROLE_NO;
-		qq_group_refresh(gc, group);
-		_qq_group_join_auth(gc, group);
+			   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"), group->ext_id);
+		msg = g_strdup_printf(_("Qun %d denied to join"), 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",
-			   group->ext_id, group->title_utf8, reply);
+			   rmd->ext_id, rmd->title_utf8, reply);
 
 		purple_notify_info(gc, _("QQ Qun Operation"), _("Failed:"), _("Join Qun, Unknow Reply"));
 	}
@@ -278,55 +284,146 @@
 void qq_group_join(PurpleConnection *gc, GHashTable *data)
 {
 	qq_data *qd;
-	gchar *ext_id_ptr;
+	gchar *ext_id_str;
+	gchar *id_str;
 	guint32 ext_id;
-	qq_group *group;
+	guint32 id;
+	qq_room_data *rmd;
 
 	g_return_if_fail(data != NULL);
 	qd = (qq_data *) gc->proto_data;
 
-	ext_id_ptr = g_hash_table_lookup(data, QQ_ROOM_KEY_EXTERNAL_ID);
-	g_return_if_fail(ext_id_ptr != NULL);
-	errno = 0;
-	ext_id = strtol(ext_id_ptr, NULL, 10);
-	if (errno != 0) {
-		purple_notify_error(gc, _("Error"),
-				_("You entered a group ID outside the acceptable range"), NULL);
+	ext_id_str = g_hash_table_lookup(data, QQ_ROOM_KEY_EXTERNAL_ID);
+	id_str = g_hash_table_lookup(data, QQ_ROOM_KEY_INTERNAL_ID);
+	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);
+		if (id != 0) {
+			rmd = qq_room_data_find(gc, id);
+			if (rmd) {
+				qq_request_room_join(gc, rmd);
+				return;
+			}
+		}
+	}
+
+	purple_debug_info("QQ", "Search and join extend id %s\n", ext_id_str);
+	if (ext_id_str == NULL) {
+		return;
+	}
+	ext_id = strtol(ext_id_str, NULL, 10);
+	if (ext_id == 0) {
 		return;
 	}
 
-	group = qq_room_search_ext_id(gc, ext_id);
-	if (group) {
-		qq_request_room_join(gc, group);
-	} else {
-		qq_set_pending_id(&qd->joining_groups, ext_id, TRUE);
-		qq_send_cmd_group_search_group(gc, ext_id);
-	}
+	qq_request_room_search(gc, ext_id, QQ_ROOM_SEARCH_FOR_JOIN);
 }
 
-void qq_group_exit(PurpleConnection *gc, GHashTable *data)
+void qq_room_quit(PurpleConnection *gc, guint32 room_id)
 {
-	gchar *id_ptr;
-	guint32 id;
-	gc_and_uid *g;
-
-	g_return_if_fail(data != NULL);
+	qq_room_req *add_req;
 
-	id_ptr = g_hash_table_lookup(data, QQ_ROOM_KEY_INTERNAL_ID);
-	id = strtol(id_ptr, NULL, 10);
-
-	g_return_if_fail(id > 0);
-
-	g = g_new0(gc_and_uid, 1);
-	g->gc = gc;
-	g->uid = id;
+	add_req = g_new0(qq_room_req, 1);
+	add_req->gc = gc;
+	add_req->id = room_id;
 
 	purple_request_action(gc, _("QQ Qun Operation"),
-			    _("Are you sure you want to leave this Qun?"),
+			    _("Quit Qun"),
 			    _("Note, if you are the creator, \nthis operation will eventually remove this Qun."),
 			    1,
 				purple_connection_get_account(gc), NULL, NULL,
-			    g, 2, _("Cancel"),
-			    G_CALLBACK(qq_do_nothing_with_gc_and_uid),
-			    _("Continue"), G_CALLBACK(_qq_group_exit_with_gc_and_id));
+			    add_req, 2, _("Cancel"),
+			    G_CALLBACK(room_join_cancel_cb),
+			    _("Continue"), G_CALLBACK(group_quit_cb));
+}
+
+/* send packet to search for qq_group */
+void qq_request_room_search(PurpleConnection *gc, guint32 ext_id, int action)
+{
+	guint8 raw_data[16] = {0};
+	gint bytes = 0;
+	guint8 type;
+
+	purple_debug_info("QQ", "Search QQ Qun %d\n", ext_id);
+	type = (ext_id == 0x00000000) ? QQ_ROOM_SEARCH_TYPE_DEMO : QQ_ROOM_SEARCH_TYPE_BY_ID;
+
+	bytes = 0;
+	bytes += qq_put8(raw_data + bytes, type);
+	bytes += qq_put32(raw_data + bytes, ext_id);
+
+	qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_SEARCH, 0, raw_data, bytes, 0, action);
+}
+
+static void add_to_roomlist(qq_data *qd, qq_room_data *rmd)
+{
+	PurpleRoomlistRoom *room;
+	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);
+	purple_roomlist_room_add_field(qd->roomlist, room, field);
+	g_snprintf(field, sizeof(field), "%d", 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);
+	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);
+	g_snprintf(field, sizeof(field), "%d", rmd->auth_type);
+	purple_roomlist_room_add_field(qd->roomlist, room, field);
+	g_snprintf(field, sizeof(field), "%d", rmd->category);
+	purple_roomlist_room_add_field(qd->roomlist, room, field);
+	purple_roomlist_room_add_field(qd->roomlist, room, rmd->title_utf8);
+	purple_roomlist_room_add(qd->roomlist, room);
+
+	purple_roomlist_set_in_progress(qd->roomlist, FALSE);
 }
+
+/* process group cmd reply "search group" */
+void qq_process_room_search(PurpleConnection *gc, guint8 *data, gint len, guint32 ship32)
+{
+	qq_data *qd;
+	qq_room_data rmd;
+	PurpleChat *chat;
+	gint bytes;
+	guint8 search_type;
+	guint16 unknown;
+
+	g_return_if_fail(data != NULL && len > 0);
+	qd = (qq_data *) gc->proto_data;
+
+	bytes = 0;
+	bytes += qq_get8(&search_type, data + bytes);
+
+	/* now it starts with group_info_entry */
+	bytes += qq_get32(&(rmd.id), data + bytes);
+	bytes += qq_get32(&(rmd.ext_id), data + bytes);
+	bytes += qq_get8(&(rmd.type8), data + bytes);
+	bytes += qq_get16(&(unknown), data + bytes);
+	bytes += qq_get16(&(unknown), data + bytes);
+	bytes += qq_get32(&(rmd.creator_uid), data + bytes);
+	bytes += qq_get16(&(unknown), data + bytes);
+	bytes += qq_get16(&(unknown), data + bytes);
+	bytes += qq_get16(&(unknown), data + bytes);
+	bytes += qq_get32(&(rmd.category), data + bytes);
+	bytes += qq_get_vstr(&(rmd.title_utf8), QQ_CHARSET_DEFAULT, data + bytes);
+	bytes += qq_get16(&(unknown), data + bytes);
+	bytes += qq_get8(&(rmd.auth_type), data + bytes);
+	bytes += qq_get_vstr(&(rmd.desc_utf8), QQ_CHARSET_DEFAULT, data + bytes);
+	/* end of one qq_group */
+	if(bytes != len) {
+		purple_debug_error("QQ",
+			"group_cmd_search_group: Dangerous error! maybe protocol changed, notify developers!");
+	}
+
+	if (ship32 == QQ_ROOM_SEARCH_FOR_JOIN) {
+		chat = qq_room_find_or_new(gc, rmd.id, rmd.ext_id);
+		g_return_if_fail(chat != NULL);
+
+		qq_room_update_chat_info(chat, &rmd);
+		qq_request_room_join(gc, &rmd);
+	} else {
+		add_to_roomlist(qd, &rmd);
+	}
+}
--- a/libpurple/protocols/qq/group_join.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/group_join.h	Thu Nov 06 03:20:05 2008 +0000
@@ -41,12 +41,19 @@
 	QQ_ROOM_AUTH_REQUEST_REJECT = 0x03
 };
 
-void qq_send_cmd_group_auth(PurpleConnection *gc, qq_group *group, guint8 opt, guint32 uid, const gchar *reason_utf8);
+enum {
+	QQ_ROOM_SEARCH_ONLY = 0,
+	QQ_ROOM_SEARCH_FOR_JOIN
+};
+
+void qq_request_room_search(PurpleConnection *gc, guint32 ext_id, int action);
+void qq_process_room_search(PurpleConnection *gc, guint8 *data, gint len, guint32 ship32);
+
+void qq_send_cmd_group_auth(PurpleConnection *gc, qq_room_data *rmd, guint8 opt, guint32 uid, const gchar *reason_utf8);
 void qq_group_join(PurpleConnection *gc, GHashTable *data);
-void qq_request_room_join(PurpleConnection *gc, qq_group *group);
-void qq_group_exit(PurpleConnection *gc, GHashTable *data);
+void qq_request_room_join(PurpleConnection *gc, qq_room_data *rmd);
+void qq_room_quit(PurpleConnection *gc, guint32 room_id);
 void qq_process_group_cmd_exit_group(guint8 *data, gint len, PurpleConnection *gc);
 void qq_process_group_cmd_join_group_auth(guint8 *data, gint len, PurpleConnection *gc);
 void qq_process_group_cmd_join_group(guint8 *data, gint len, PurpleConnection *gc);
-
 #endif
--- a/libpurple/protocols/qq/group_opt.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/group_opt.c	Thu Nov 06 03:20:05 2008 +0000
@@ -30,12 +30,12 @@
 
 #include "buddy_info.h"
 #include "char_conv.h"
-#include "group_find.h"
 #include "group_internal.h"
 #include "group_info.h"
 #include "group_join.h"
+#include "group_im.h"
 #include "group_opt.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "packet_parse.h"
 #include "qq_network.h"
 #include "qq_process.h"
@@ -57,7 +57,7 @@
 	qsort (list, i, sizeof (guint32), _compare_guint32);
 }
 
-static void _qq_group_member_opt(PurpleConnection *gc, qq_group *group, gint operation, guint32 *members)
+static void _qq_group_member_opt(PurpleConnection *gc, qq_room_data *rmd, gint operation, guint32 *members)
 {
 	guint8 *data;
 	gint i, count, data_len;
@@ -74,80 +74,71 @@
 	for (i = 0; i < count; i++)
 		bytes += qq_put32(data + bytes, members[i]);
 
-	qq_send_room_cmd(gc, QQ_ROOM_CMD_MEMBER_OPT, group->id, data, bytes);
+	qq_send_room_cmd(gc, QQ_ROOM_CMD_MEMBER_OPT, rmd->id, data, bytes);
 }
 
-static void _qq_group_do_nothing_with_struct(group_member_opt *g)
+static void room_req_cancel_cb(qq_room_req *add_req)
 {
-	if (g != NULL)
-		g_free(g);
+	if (add_req != NULL)
+		g_free(add_req);
 }
 
-static void _qq_group_reject_application_real(group_member_opt *g, gchar *msg_utf8)
+static void member_join_authorize_cb(gpointer data)
 {
-	qq_group *group;
-	g_return_if_fail(g != NULL && g->gc != NULL && g->id > 0 && g->member > 0);
-	group = qq_room_search_id(g->gc, g->id);
-	g_return_if_fail(group != NULL);
-	qq_send_cmd_group_auth(g->gc, group, QQ_ROOM_AUTH_REQUEST_REJECT, g->member, msg_utf8);
-	g_free(g);
-}
+	qq_room_req *add_req = (qq_room_req *)data;
+	qq_room_data *rmd;
+	g_return_if_fail(add_req != NULL && add_req->gc != NULL);
+	g_return_if_fail(add_req->id > 0 && add_req->member > 0);
+	rmd = qq_room_data_find(add_req->gc, add_req->id);
+	g_return_if_fail(rmd != NULL);
 
-void qq_group_search_application_with_struct(group_member_opt *g)
-{
-	g_return_if_fail(g != NULL && g->gc != NULL && g->member > 0);
-
-	qq_send_packet_get_info(g->gc, g->member, TRUE);	/* we want to see window */
-	purple_request_action(g->gc, NULL, _("Do you want to approve the request?"), "",
-				PURPLE_DEFAULT_ACTION_NONE,
-				purple_connection_get_account(g->gc), NULL, NULL,
-				g, 2,
-				_("Reject"), G_CALLBACK(qq_group_reject_application_with_struct),
-				_("Approve"), G_CALLBACK(qq_group_approve_application_with_struct));
+	qq_send_cmd_group_auth(add_req->gc, rmd, QQ_ROOM_AUTH_REQUEST_APPROVE, add_req->member, "");
+	qq_room_buddy_find_or_new(add_req->gc, rmd, add_req->member);
+	g_free(add_req);
 }
 
-void qq_group_reject_application_with_struct(group_member_opt *g)
+static void member_join_deny_reason_cb(qq_room_req *add_req, gchar *msg_utf8)
 {
-	gchar *msg1, *msg2, *nombre;
-	g_return_if_fail(g != NULL && g->gc != NULL && g->member > 0);
-
-	msg1 = g_strdup_printf(_("You rejected %d's request"), g->member);
-	msg2 = g_strdup(_("Message:"));
+	qq_room_data *rmd;
+	g_return_if_fail(add_req != NULL && add_req->gc != NULL);
+	g_return_if_fail(add_req->id > 0 && add_req->member > 0);
+	rmd = qq_room_data_find(add_req->gc, add_req->id);
+	g_return_if_fail(rmd != NULL);
+	qq_send_cmd_group_auth(add_req->gc, rmd, QQ_ROOM_AUTH_REQUEST_REJECT, add_req->member, msg_utf8);
+	g_free(add_req);
+}
 
-	nombre = uid_to_purple_name(g->member);
-	purple_request_input(g->gc, /* title */ NULL, msg1, msg2,
-			   _("Sorry, you are not my style..."), /* multiline */ TRUE, /* masked */ FALSE,
-			   /* hint */ NULL,
-			   _("Send"), G_CALLBACK(_qq_group_reject_application_real),
-			   _("Cancel"), G_CALLBACK(_qq_group_do_nothing_with_struct),
-			   purple_connection_get_account(g->gc), nombre, NULL,
-			   g);
-
-	g_free(msg1);
-	g_free(msg2);
-	g_free(nombre);
+static void member_join_deny_noreason_cb(qq_room_req *add_req, gchar *msg_utf8)
+{
+	member_join_deny_reason_cb(add_req, NULL);
 }
 
-void qq_group_approve_application_with_struct(group_member_opt *g)
+static void member_join_deny_cb(gpointer data)
 {
-	qq_group *group;
-	g_return_if_fail(g != NULL && g->gc != NULL && g->id > 0 && g->member > 0);
-	group = qq_room_search_id(g->gc, g->id);
-	g_return_if_fail(group != NULL);
-	qq_send_cmd_group_auth(g->gc, group, QQ_ROOM_AUTH_REQUEST_APPROVE, g->member, "");
-	qq_group_find_or_add_member(g->gc, group, g->member);
-	g_free(g);
+	qq_room_req *add_req = (qq_room_req *)data;
+	gchar *who;
+	g_return_if_fail(add_req != NULL && add_req->gc != NULL);
+	g_return_if_fail(add_req->id > 0 && add_req->member > 0);
+
+	who = uid_to_purple_name(add_req->member);
+	purple_request_input(add_req->gc, NULL, _("Authorization denied message:"),
+			NULL, _("Sorry, you are not our style ..."), TRUE, FALSE, NULL,
+			_("OK"), G_CALLBACK(member_join_deny_reason_cb),
+			_("Cancel"), G_CALLBACK(member_join_deny_noreason_cb),
+			purple_connection_get_account(add_req->gc), who, NULL,
+			add_req);
+	g_free(who);
 }
 
-void qq_group_modify_members(PurpleConnection *gc, qq_group *group, guint32 *new_members)
+void qq_group_modify_members(PurpleConnection *gc, qq_room_data *rmd, guint32 *new_members)
 {
 	guint32 *old_members, *del_members, *add_members;
-	qq_buddy *q_bud;
+	qq_buddy_data *bd;
 	qq_data *qd;
 	gint i = 0, old = 0, new = 0, del = 0, add = 0;
 	GList *list;
 
-	g_return_if_fail(group != NULL);
+	g_return_if_fail(rmd != NULL);
 	qd = (qq_data *) gc->proto_data;
 	if (new_members[0] == 0xffffffff)
 		return;
@@ -157,11 +148,11 @@
 	add_members = g_newa(guint32, QQ_QUN_MEMBER_MAX);
 
 	/* construct the old member list */
-	list = group->members;
+	list = rmd->members;
 	while (list != NULL) {
-		q_bud = (qq_buddy *) list->data;
-		if (q_bud != NULL)
-			old_members[i++] = q_bud->uid;
+		bd = (qq_buddy_data *) list->data;
+		if (bd != NULL)
+			old_members[i++] = bd->uid;
 		list = list->next;
 	}
 	old_members[i] = 0xffffffff;	/* this is the end */
@@ -185,21 +176,22 @@
 	del_members[del] = add_members[add] = 0xffffffff;
 
 	for (i = 0; i < del; i++)
-		qq_group_remove_member_by_uid(group, del_members[i]);
+		qq_room_buddy_remove(rmd, del_members[i]);
 	for (i = 0; i < add; i++)
-		qq_group_find_or_add_member(gc, group, add_members[i]);
+		qq_room_buddy_find_or_new(gc, rmd, add_members[i]);
 
 	if (del > 0)
-		_qq_group_member_opt(gc, group, QQ_ROOM_MEMBER_DEL, del_members);
+		_qq_group_member_opt(gc, rmd, QQ_ROOM_MEMBER_DEL, del_members);
 	if (add > 0)
-		_qq_group_member_opt(gc, group, QQ_ROOM_MEMBER_ADD, add_members);
+		_qq_group_member_opt(gc, rmd, QQ_ROOM_MEMBER_ADD, add_members);
 }
 
 void qq_group_process_modify_members_reply(guint8 *data, gint len, PurpleConnection *gc)
 {
 	gint bytes;
 	guint32 id;
-	qq_group *group;
+	time_t now = time(NULL);
+	qq_room_data *rmd;
 	g_return_if_fail(data != NULL);
 
 	bytes = 0;
@@ -207,82 +199,60 @@
 	g_return_if_fail(id > 0);
 
 	/* we should have its info locally */
-	group = qq_room_search_id(gc, id);
-	g_return_if_fail(group != NULL);
+	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", group->ext_id);
+	purple_debug_info("QQ", "Succeed in modify members for room %d\n", rmd->ext_id);
 
-	purple_notify_info(gc, _("QQ Qun Operation"), _("Successed:"), _("Change Qun member"));
+	qq_room_got_chat_in(gc, id, 0, _("Successed changing Qun member"), now);
 }
 
-void qq_room_change_info(PurpleConnection *gc, qq_group *group)
+void qq_room_change_info(PurpleConnection *gc, qq_room_data *rmd)
 {
-	guint8 *data;
-	gint data_len;
+	guint8 data[MAX_PACKET_SIZE - 16];
 	gint bytes;
-	gchar *group_name, *group_desc, *notice;
 
-	g_return_if_fail(group != NULL);
+	g_return_if_fail(rmd != NULL);
 
-	group_name = group->title_utf8 == NULL ? "" : utf8_to_qq(group->title_utf8, QQ_CHARSET_DEFAULT);
-	group_desc = group->desc_utf8 == NULL ? "" : utf8_to_qq(group->desc_utf8, QQ_CHARSET_DEFAULT);
-	notice = group->notice_utf8 == NULL ? "" : utf8_to_qq(group->notice_utf8, QQ_CHARSET_DEFAULT);
-
-	data_len = 64 + strlen(group_name) + strlen(group_desc) + strlen(notice);
-	data = g_newa(guint8, data_len);
 	bytes = 0;
 	/* 005-005 */
 	bytes += qq_put8(data + bytes, 0x01);
 	/* 006-006 */
-	bytes += qq_put8(data + bytes, group->auth_type);
+	bytes += qq_put8(data + bytes, rmd->auth_type);
 	/* 007-008 */
 	bytes += qq_put16(data + bytes, 0x0000);
 	/* 009-010 */
-	bytes += qq_put16(data + bytes, group->category);
+	bytes += qq_put16(data + bytes, rmd->category);
 
-	bytes += qq_put8(data + bytes, strlen(group_name));
-	bytes += qq_putdata(data + bytes, (guint8 *) group_name, strlen(group_name));
+	bytes += qq_put_vstr(data + bytes, rmd->title_utf8, QQ_CHARSET_DEFAULT);
 
 	bytes += qq_put16(data + bytes, 0x0000);
 
-	bytes += qq_put8(data + bytes, strlen(notice));
-	bytes += qq_putdata(data+ bytes, (guint8 *) notice, strlen(notice));
-
-	bytes += qq_put8(data + bytes, strlen(group_desc));
-	bytes += qq_putdata(data + bytes, (guint8 *) group_desc, strlen(group_desc));
+	bytes += qq_put_vstr(data + bytes, rmd->notice_utf8, QQ_CHARSET_DEFAULT);
+	bytes += qq_put_vstr(data + bytes, rmd->desc_utf8, QQ_CHARSET_DEFAULT);
 
-	if (bytes > data_len) {
-		purple_debug_error("QQ",
-			   "Overflow in qq_room_change_info, max %d bytes, now %d bytes\n",
-			   data_len, bytes);
-		return;
-	}
-	qq_send_room_cmd(gc, QQ_ROOM_CMD_CHANGE_INFO, group->id, data, bytes);
+	qq_send_room_cmd(gc, QQ_ROOM_CMD_CHANGE_INFO, rmd->id, data, bytes);
 }
 
 void qq_group_process_modify_info_reply(guint8 *data, gint len, PurpleConnection *gc)
 {
 	gint bytes;
 	guint32 id;
-	qq_group *group;
+	time_t now = time(NULL);
+
 	g_return_if_fail(data != NULL);
 
 	bytes = 0;
 	bytes += qq_get32(&id, data + bytes);
 	g_return_if_fail(id > 0);
 
-	/* we should have its info locally */
-	group = qq_room_search_id(gc, id);
-	g_return_if_fail(group != NULL);
+	purple_debug_info("QQ", "Succeed modify room info of %d\n", id);
 
-	purple_debug_info("QQ", "Succeed in modify info for Qun %d\n", group->ext_id);
-	qq_group_refresh(gc, group);
-
-	purple_notify_info(gc, _("QQ Qun Operation"), _("Successed:"), _("Change Qun information"));
+	qq_room_got_chat_in(gc, id, 0, _("Successed changing Qun information"), now);
 }
 
-/* we create a very simple group first, and then let the user to modify */
-void qq_room_create_new(PurpleConnection *gc, const gchar *name)
+/* we create a very simple room first, and then let the user to modify */
+void qq_create_room(PurpleConnection *gc, const gchar *name)
 {
 	guint8 *data;
 	gint data_len;
@@ -322,25 +292,32 @@
 	qq_send_room_cmd_noid(gc, QQ_ROOM_CMD_CREATE, data, bytes);
 }
 
-static void qq_group_setup_with_gc_and_uid(gc_and_uid *g)
+static void room_create_cb(qq_room_req *add_req)
 {
-	qq_group *group;
-	g_return_if_fail(g != NULL && g->gc != NULL && g->uid > 0);
+	qq_room_data *rmd;
+	g_return_if_fail(add_req != NULL);
+	if (add_req->gc == NULL || add_req->id == 0) {
+		g_free(add_req);
+		return;
+	}
 
-	group = qq_room_search_id(g->gc, g->uid);
-	g_return_if_fail(group != NULL);
+	rmd = qq_room_data_find(add_req->gc, add_req->id);
+	if (rmd == NULL) {
+		g_free(add_req);
+		return;
+	}
 
 	/* TODO insert UI code here */
-	/* qq_group_detail_window_show(g->gc, group); */
-	g_free(g);
+	/* qq_group_detail_window_show(g->gc, rmd); */
+	g_free(add_req);
 }
 
 void qq_group_process_create_group_reply(guint8 *data, gint len, PurpleConnection *gc)
 {
 	gint bytes;
 	guint32 id, ext_id;
-	qq_group *group;
-	gc_and_uid *g;
+	qq_room_data *rmd;
+	qq_room_req *add_req;
 	qq_data *qd;
 
 	g_return_if_fail(data != NULL);
@@ -352,36 +329,37 @@
 	bytes += qq_get32(&ext_id, data + bytes);
 	g_return_if_fail(id > 0 && ext_id);
 
-	group = qq_group_create_internal_record(gc, id, ext_id, NULL);
-	group->my_role = QQ_ROOM_ROLE_ADMIN;
-	group->creator_uid = qd->uid;
-	qq_group_refresh(gc, group);
+	qq_room_find_or_new(gc, id, ext_id);
+	rmd = qq_room_data_find(gc, id);
+	g_return_if_fail(rmd != NULL);
+
+	rmd->my_role = QQ_ROOM_ROLE_ADMIN;
+	rmd->creator_uid = qd->uid;
 
 	qq_send_room_cmd_only(gc, QQ_ROOM_CMD_ACTIVATE, id);
-	qq_update_room(gc, 0, group->id);
+	qq_update_room(gc, 0, rmd->id);
 
-	purple_debug_info("QQ", "Succeed in create Qun, external ID %d\n", group->ext_id);
+	purple_debug_info("QQ", "Succeed in create Qun, external ID %d\n", rmd->ext_id);
 
-	g = g_new0(gc_and_uid, 1);
-	g->gc = gc;
-	g->uid = id;
+	add_req = g_new0(qq_room_req, 1);
+	add_req->gc = gc;
+	add_req->id = id;
 
 	purple_request_action(gc, _("QQ Qun Operation"),
 			    _("You have successfully created a Qun"),
-			    _
-			    ("Would you like to set up the detail information now?"),
+			    _("Would you like to set up the detail information now?"),
 			    1,
 				purple_connection_get_account(gc), NULL, NULL,
-				g, 2,
-				_("Setup"), G_CALLBACK(qq_group_setup_with_gc_and_uid),
-			    _("Cancel"), G_CALLBACK(qq_do_nothing_with_gc_and_uid));
+				add_req, 2,
+				_("Setup"), G_CALLBACK(room_create_cb),
+			    _("Cancel"), G_CALLBACK(room_req_cancel_cb));
 }
 
 void qq_group_process_activate_group_reply(guint8 *data, gint len, PurpleConnection *gc)
 {
 	gint bytes;
 	guint32 id;
-	qq_group *group;
+	qq_room_data *rmd;
 	g_return_if_fail(data != NULL);
 
 	bytes = 0;
@@ -389,17 +367,17 @@
 	g_return_if_fail(id > 0);
 
 	/* we should have its info locally */
-	group = qq_room_search_id(gc, id);
-	g_return_if_fail(group != NULL);
+	rmd = qq_room_data_find(gc, id);
+	g_return_if_fail(rmd != NULL);
 
-	purple_debug_info("QQ", "Succeed in activate Qun %d\n", group->ext_id);
+	purple_debug_info("QQ", "Succeed in activate Qun %d\n", rmd->ext_id);
 }
 
 void qq_group_manage_group(PurpleConnection *gc, GHashTable *data)
 {
 	gchar *id_ptr;
 	guint32 id;
-	qq_group *group;
+	qq_room_data *rmd;
 
 	g_return_if_fail(data != NULL);
 
@@ -407,9 +385,211 @@
 	id = strtol(id_ptr, NULL, 10);
 	g_return_if_fail(id > 0);
 
-	group = qq_room_search_id(gc, id);
-	g_return_if_fail(group != NULL);
+	rmd = qq_room_data_find(gc, id);
+	g_return_if_fail(rmd != NULL);
 
 	/* XXX insert UI code here */
-	/* qq_group_detail_window_show(gc, group); */
+	/* qq_group_detail_window_show(gc, rmd); */
+}
+
+/* receive an application to join the group */
+void qq_process_room_buddy_request_join(guint8 *data, gint len, guint32 id, PurpleConnection *gc)
+{
+	guint32 ext_id, member_id;
+	guint8 type8;
+	gchar *msg, *reason;
+	qq_room_req *add_req;
+	gchar *who;
+	gint bytes = 0;
+	qq_room_data *rmd;
+	time_t now = time(NULL);
+
+	g_return_if_fail(id > 0 && data != NULL && len > 0);
+
+	/* FIXME: check length here */
+
+	bytes += qq_get32(&ext_id, data + bytes);
+	bytes += qq_get8(&type8, data + bytes);
+	bytes += qq_get32(&member_id, data + bytes);
+
+	g_return_if_fail(ext_id > 0 && member_id > 0);
+
+	bytes += qq_get_vstr(&reason, QQ_CHARSET_DEFAULT, data + bytes);
+
+	add_req = g_new0(qq_room_req, 1);
+	add_req->gc = gc;
+	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);
+
+	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"),
+				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, "");
+		g_free(msg);
+		g_free(reason);
+		return;
+	}
+
+	if (purple_prefs_get_bool("/plugins/prpl/qq/auto_get_authorize_info")) {
+		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);
+
+	purple_request_action(gc, _("QQ Qun Operation"),
+			msg, reason,
+			PURPLE_DEFAULT_ACTION_NONE,
+			purple_connection_get_account(gc), who, NULL,
+			add_req, 2,
+			_("Deny"), G_CALLBACK(member_join_deny_cb),
+			_("Authorize"), G_CALLBACK(member_join_authorize_cb));
+
+	g_free(who);
+	g_free(msg);
+	g_free(reason);
+	g_free(reason);
+}
+
+/* the request to join a group is rejected */
+void qq_process_room_buddy_rejected(guint8 *data, gint len, guint32 id, PurpleConnection *gc)
+{
+	guint32 ext_id, admin_uid;
+	guint8 type8;
+	gchar *msg, *reason;
+	qq_room_data *rmd;
+	gint bytes;
+
+	g_return_if_fail(data != NULL && len > 0);
+
+	/* FIXME: check length here */
+	bytes = 0;
+	bytes += qq_get32(&ext_id, data + bytes);
+	bytes += qq_get8(&type8, data + bytes);
+	bytes += qq_get32(&admin_uid, data + bytes);
+
+	g_return_if_fail(ext_id > 0 && admin_uid > 0);
+
+	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);
+
+	purple_notify_warning(gc, _("QQ Qun Operation"), msg, reason);
+
+	qq_room_find_or_new(gc, id, ext_id);
+	rmd = qq_room_data_find(gc, id);
+	if (rmd != NULL) {
+		rmd->my_role = QQ_ROOM_ROLE_NO;
+	}
+
+	g_free(msg);
+	g_free(reason);
 }
+
+/* the request to join a group is approved */
+void qq_process_room_buddy_approved(guint8 *data, gint len, guint32 id, PurpleConnection *gc)
+{
+	guint32 ext_id, admin_uid;
+	guint8 type8;
+	gchar *msg, *reason;
+	qq_room_data *rmd;
+	gint bytes;
+	time_t now;
+
+	g_return_if_fail(data != NULL && len > 0);
+
+	/* FIXME: check length here */
+	bytes = 0;
+	bytes += qq_get32(&ext_id, data + bytes);
+	bytes += qq_get8(&type8, data + bytes);
+	bytes += qq_get32(&admin_uid, data + bytes);
+
+	g_return_if_fail(ext_id > 0 && admin_uid > 0);
+	/* it is also a "æ— " here, so do not display */
+	bytes += qq_get_vstr(&reason, QQ_CHARSET_DEFAULT, data + bytes);
+
+	qq_room_find_or_new(gc, id, ext_id);
+	rmd = qq_room_data_find(gc, id);
+	if (rmd != NULL) {
+		rmd->my_role = QQ_ROOM_ROLE_YES;
+	}
+
+	msg = g_strdup_printf(_("<b>Joinning Qun %d is approved by Admin %d for %s</b>"),
+			ext_id, admin_uid, reason);
+	now = time(NULL);
+	qq_room_got_chat_in(gc, id, 0, msg, now);
+
+	g_free(msg);
+	g_free(reason);
+}
+
+/* process the packet when removed from a group */
+void qq_process_room_buddy_removed(guint8 *data, gint len, guint32 id, PurpleConnection *gc)
+{
+	guint32 ext_id, uid;
+	guint8 type8;
+	gchar *msg;
+	qq_room_data *rmd;
+	gint bytes = 0;
+	time_t now = time(NULL);
+
+	g_return_if_fail(data != NULL && len > 0);
+
+	/* FIXME: check length here */
+	bytes = 0;
+	bytes += qq_get32(&ext_id, data + bytes);
+	bytes += qq_get8(&type8, data + bytes);
+	bytes += qq_get32(&uid, data + bytes);
+
+	g_return_if_fail(ext_id > 0 && uid > 0);
+
+	qq_room_find_or_new(gc, id, ext_id);
+	rmd = qq_room_data_find(gc, id);
+	if (rmd != NULL) {
+		rmd->my_role = QQ_ROOM_ROLE_NO;
+	}
+
+	msg = g_strdup_printf(_("<b>Removed buddy %d.</b>"), uid);
+	qq_room_got_chat_in(gc, id, 0, msg, now);
+	g_free(msg);
+}
+
+/* process the packet when added to a group */
+void qq_process_room_buddy_joined(guint8 *data, gint len, guint32 id, PurpleConnection *gc)
+{
+	guint32 ext_id, uid;
+	guint8 type8;
+	qq_room_data *rmd;
+	gint bytes;
+	gchar *msg;
+	time_t now = time(NULL);
+
+	g_return_if_fail(data != NULL && len > 0);
+
+	/* FIXME: check length here */
+	bytes = 0;
+	bytes += qq_get32(&ext_id, data + bytes);
+	bytes += qq_get8(&type8, data + bytes);
+	bytes += qq_get32(&uid, data + bytes);
+
+	g_return_if_fail(ext_id > 0 && id > 0);
+
+	qq_room_find_or_new(gc, id, ext_id);
+	rmd = qq_room_data_find(gc, id);
+	g_return_if_fail(rmd != NULL);
+
+	rmd->my_role = QQ_ROOM_ROLE_YES;
+
+	qq_update_room(gc, 0, rmd->id);
+
+	msg = g_strdup_printf(_("<b>New buddy %d joined.</b>"), uid);
+	qq_room_got_chat_in(gc, id, 0, msg, now);
+	g_free(msg);
+}
+
--- a/libpurple/protocols/qq/group_opt.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/group_opt.h	Thu Nov 06 03:20:05 2008 +0000
@@ -31,11 +31,11 @@
 
 #define QQ_QUN_MEMBER_MAX       80	/* max number of the group */
 
-typedef struct _group_member_opt {
+typedef struct _qq_room_req {
 	PurpleConnection *gc;
 	guint32 id;
 	guint32 member;
-} group_member_opt;
+} qq_room_req;
 
 enum {
 	QQ_ROOM_TYPE_PERMANENT = 0x01,
@@ -47,18 +47,19 @@
 	QQ_ROOM_MEMBER_DEL
 };
 
-void qq_group_modify_members(PurpleConnection *gc, qq_group *group, guint32 *new_members);
-void qq_room_change_info(PurpleConnection *gc, qq_group *group);
+void qq_group_modify_members(PurpleConnection *gc, qq_room_data *rmd, guint32 *new_members);
+void qq_room_change_info(PurpleConnection *gc, qq_room_data *rmd);
 
-void qq_group_approve_application_with_struct(group_member_opt *g);
-void qq_group_reject_application_with_struct(group_member_opt *g);
-void qq_group_search_application_with_struct(group_member_opt *g);
-
+void qq_create_room(PurpleConnection *gc, const gchar *name);
 void qq_group_process_modify_info_reply(guint8 *data, gint len, PurpleConnection *gc);
 void qq_group_process_modify_members_reply(guint8 *data, gint len, PurpleConnection *gc);
 void qq_group_manage_group(PurpleConnection *gc, GHashTable *data);
-void qq_room_create_new(PurpleConnection *gc, const gchar *name);
 void qq_group_process_activate_group_reply(guint8 *data, gint len, PurpleConnection *gc);
 void qq_group_process_create_group_reply(guint8 *data, gint len, PurpleConnection *gc);
 
+void qq_process_room_buddy_request_join(guint8 *data, gint len, guint32 id, PurpleConnection *gc);
+void qq_process_room_buddy_rejected(guint8 *data, gint len, guint32 id, PurpleConnection *gc);
+void qq_process_room_buddy_approved(guint8 *data, gint len, guint32 id, PurpleConnection *gc);
+void qq_process_room_buddy_removed(guint8 *data, gint len, guint32 id, PurpleConnection *gc);
+void qq_process_room_buddy_joined(guint8 *data, gint len, guint32 id, PurpleConnection *gc);
 #endif
--- a/libpurple/protocols/qq/group_search.c	Thu Nov 06 02:21:16 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,133 +0,0 @@
-/**
- * @file group_search.c
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#include "internal.h"
-
-#include "debug.h"
-
-#include "char_conv.h"
-#include "group_find.h"
-#include "group_free.h"
-#include "group_internal.h"
-#include "group_join.h"
-#include "group_search.h"
-#include "utils.h"
-#include "header_info.h"
-#include "packet_parse.h"
-#include "qq_network.h"
-
-enum {
-	QQ_ROOM_SEARCH_TYPE_BY_ID = 0x01,
-	QQ_ROOM_SEARCH_TYPE_DEMO = 0x02
-};
-
-/* send packet to search for qq_group */
-void qq_send_cmd_group_search_group(PurpleConnection *gc, guint32 ext_id)
-{
-	guint8 raw_data[16] = {0};
-	gint bytes = 0;
-	guint8 type;
-
-	type = (ext_id == 0x00000000) ? QQ_ROOM_SEARCH_TYPE_DEMO : QQ_ROOM_SEARCH_TYPE_BY_ID;
-
-	bytes = 0;
-	bytes += qq_put8(raw_data + bytes, type);
-	bytes += qq_put32(raw_data + bytes, ext_id);
-
-	qq_send_room_cmd_noid(gc, QQ_ROOM_CMD_SEARCH, raw_data, bytes);
-}
-
-static void _qq_setup_roomlist(qq_data *qd, qq_group *group)
-{
-	PurpleRoomlistRoom *room;
-	gchar field[11];
-
-	room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, group->title_utf8, NULL);
-	g_snprintf(field, sizeof(field), "%d", group->ext_id);
-	purple_roomlist_room_add_field(qd->roomlist, room, field);
-	g_snprintf(field, sizeof(field), "%d", group->creator_uid);
-	purple_roomlist_room_add_field(qd->roomlist, room, field);
-	purple_roomlist_room_add_field(qd->roomlist, room, group->desc_utf8);
-	g_snprintf(field, sizeof(field), "%d", group->id);
-	purple_roomlist_room_add_field(qd->roomlist, room, field);
-	g_snprintf(field, sizeof(field), "%d", group->type8);
-	purple_roomlist_room_add_field(qd->roomlist, room, field);
-	g_snprintf(field, sizeof(field), "%d", group->auth_type);
-	purple_roomlist_room_add_field(qd->roomlist, room, field);
-	g_snprintf(field, sizeof(field), "%d", group->category);
-	purple_roomlist_room_add_field(qd->roomlist, room, field);
-	purple_roomlist_room_add_field(qd->roomlist, room, group->title_utf8);
-	purple_roomlist_room_add(qd->roomlist, room);
-
-	purple_roomlist_set_in_progress(qd->roomlist, FALSE);
-}
-
-/* process group cmd reply "search group" */
-void qq_process_group_cmd_search_group(guint8 *data, gint len, PurpleConnection *gc)
-{
-	gint bytes;
-	guint8 search_type;
-	guint16 unknown;
-	qq_group group;
-	qq_data *qd;
-	GSList *pending_id;
-
-	g_return_if_fail(data != NULL && len > 0);
-	qd = (qq_data *) gc->proto_data;
-
-	bytes = 0;
-	bytes += qq_get8(&search_type, data + bytes);
-
-	/* now it starts with group_info_entry */
-	bytes += qq_get32(&(group.id), data + bytes);
-	bytes += qq_get32(&(group.ext_id), data + bytes);
-	bytes += qq_get8(&(group.type8), data + bytes);
-	bytes += qq_get16(&(unknown), data + bytes);
-	bytes += qq_get16(&(unknown), data + bytes);
-	bytes += qq_get32(&(group.creator_uid), data + bytes);
-	bytes += qq_get16(&(unknown), data + bytes);
-	bytes += qq_get16(&(unknown), data + bytes);
-	bytes += qq_get16(&(unknown), data + bytes);
-	bytes += qq_get32(&(group.category), data + bytes);
-	bytes += convert_as_pascal_string(data + bytes, &(group.title_utf8), QQ_CHARSET_DEFAULT);
-	bytes += qq_get16(&(unknown), data + bytes);
-	bytes += qq_get8(&(group.auth_type), data + bytes);
-	bytes += convert_as_pascal_string(data + bytes, &(group.desc_utf8), QQ_CHARSET_DEFAULT);
-	/* end of one qq_group */
-	if(bytes != len) {
-		purple_debug_error("QQ",
-			"group_cmd_search_group: Dangerous error! maybe protocol changed, notify developers!");
-	}
-
-	pending_id = qq_get_pending_id(qd->joining_groups, group.ext_id);
-	if (pending_id != NULL) {
-		qq_set_pending_id(&qd->joining_groups, group.ext_id, FALSE);
-		if (qq_room_search_id(gc, group.id) == NULL)
-			qq_group_create_internal_record(gc,
-					group.id, group.ext_id, group.title_utf8);
-		qq_request_room_join(gc, &group);
-	} else {
-		_qq_setup_roomlist(qd, &group);
-	}
-}
--- a/libpurple/protocols/qq/group_search.h	Thu Nov 06 02:21:16 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-/**
- * @file group_search.h
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#ifndef _QQ_GROUP_SEARCH_H_
-#define _QQ_GROUP_SEARCH_H_
-
-#include <glib.h>
-#include "connection.h"
-
-void qq_send_cmd_group_search_group(PurpleConnection *gc, guint32 external_group_id);
-void qq_process_group_cmd_search_group(guint8 *data, gint len, PurpleConnection *gc);
-
-#endif
--- a/libpurple/protocols/qq/header_info.c	Thu Nov 06 02:21:16 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,236 +0,0 @@
-/**
- * @file header_info.c
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#include "internal.h"
-
-#include "header_info.h"
-
-#define QQ_CLIENT_062E 0x062e	/* GB QQ2000c build 0630 */
-#define QQ_CLIENT_072E 0x072e	/* EN QQ2000c build 0305 */
-#define QQ_CLIENT_0801 0x0801	/* EN QQ2000c build 0630 */
-#define QQ_CLIENT_0A1D 0x0a1d	/* GB QQ2003c build 0808 */
-#define QQ_CLIENT_0B07 0x0b07	/* GB QQ2003c build 0925 */
-#define QQ_CLIENT_0B2F 0x0b2f	/* GB QQ2003iii build 0117 */
-#define QQ_CLIENT_0B35 0x0b35	/* GB QQ2003iii build 0304 (offical release) */
-#define QQ_CLIENT_0B37 0x0b37	/* GB QQ2003iii build 0304 (April 05 updates) */
-#define QQ_CLIENT_0E1B 0x0e1b	/* QQ2005 ? */
-#define QQ_CLIENT_0E35 0x0e35	/* EN QQ2005 V05.0.200.020 */
-#define QQ_CLIENT_0F15 0x0f15	/* QQ2006 Spring Festival build */
-#define QQ_CLIENT_0F5F 0x0f5f	/* QQ2006 final build */
-
-#define QQ_CLIENT_0C0B 0x0C0B	/* QQ2004 */
-#define QQ_CLIENT_0C0D 0x0C0D	/* QQ2004 preview*/
-#define QQ_CLIENT_0C21 0x0C21	/* QQ2004 */
-#define QQ_CLIENT_0C49 0x0C49	/* QQ2004II */
-#define QQ_CLIENT_0D05 0x0D05	/* QQ2005 beta1 */
-#define QQ_CLIENT_0D51 0x0D51	/* QQ2005 beta2 */
-#define QQ_CLIENT_0D61 0x0D61	/* QQ2005 */
-#define QQ_CLIENT_05A5 0x05A5	/* ? */
-#define QQ_CLIENT_05F1 0x0F15	/* QQ2006 Spring Festival */
-#define QQ_CLIENT_0F4B 0x0F4B	/* QQ2006 Beta 3  */
-
-#define QQ_CLIENT_1105 0x1105	/* QQ2007 beta4*/
-#define QQ_CLIENT_111D 0x111D	/* QQ2007 */
-#define QQ_CLIENT_115B 0x115B	/* QQ2008 */
-#define QQ_CLIENT_1203 0x1203	/* QQ2008 */
-#define QQ_CLIENT_1205 0x1205	/* QQ2008 */
-#define QQ_CLIENT_120B 0x120B	/* QQ2008 July 8.0.978.400 */
-#define QQ_CLIENT_1412 0x1412	/* QQMac 1.0 preview1 build 670 */
-#define QQ_CLIENT_1441 0x1441	/* QQ2009 preview2 */
-
-#define QQ_SERVER_0100 0x0100	/* server */
-
-
-/* given source tag, return its description accordingly */
-const gchar *qq_get_ver_desc(gint source)
-{
-	switch (source) {
-	case QQ_CLIENT_062E:
-		return "GB QQ2000c build 0630";
-	case QQ_CLIENT_072E:
-		return "En QQ2000c build 0305";
-	case QQ_CLIENT_0801:
-		return "En QQ2000c build 0630";
-	case QQ_CLIENT_0A1D:
-		return "GB QQ2003ii build 0808";
-	case QQ_CLIENT_0B07:
-		return "GB QQ2003ii build 0925";
-	case QQ_CLIENT_0B2F:
-		return "GB QQ2003iii build 0117";
-	case QQ_CLIENT_0B35:
-		return "GB QQ2003iii build 0304";
-	case QQ_CLIENT_0B37:
-		return "GB QQ2003iii build 0304 (April 5 update)";
-	case QQ_CLIENT_0C0B:
-		return "QQ2004";
-	case QQ_CLIENT_0C0D:
-		return "QQ2004 preview";
-	case QQ_CLIENT_0C21:
-		return "QQ2004";
-	case QQ_CLIENT_0C49:
-		return "QQ2004II";
-	case QQ_CLIENT_0D05:
-		return "QQ2005 beta1";
-	case QQ_CLIENT_0D51:
-		return "QQ2005 beta2";
-	case QQ_CLIENT_0D61:
-		return "QQ2005";
-	case QQ_CLIENT_0E1B:
-		return "QQ2005 or QQ2006";
-	case QQ_CLIENT_0E35:
-		return "En QQ2005 V05.0.200.020";
-	case QQ_CLIENT_0F15:
-		return "QQ2006 Spring Festival";
-	case QQ_CLIENT_0F4B:
-		return "QQ2006 beta3";
-	case QQ_CLIENT_0F5F:
-		return "QQ2006 final build";
-	case QQ_CLIENT_1105:
-		return "QQ2007 beta4";
-	case QQ_CLIENT_111D:
-		return "QQ2007";
-	case QQ_CLIENT_115B:
-	case QQ_CLIENT_1203:
-	case QQ_CLIENT_1205:
-	case QQ_CLIENT_120B:
-		return "QQ2008";
-	case QQ_CLIENT_1412:
-		return "QQMac 1.0 preview1 build 670";
-	case QQ_CLIENT_1441:
-		return "QQ2009 preview2";
-	case QQ_SERVER_0100:
-		return "QQ Server 0100";
-	default:
-		return "Unknown Version";
-	}
-}
-
-/* given command alias, return the command name accordingly */
-const gchar *qq_get_cmd_desc(gint cmd)
-{
-	switch (cmd) {
-	case QQ_CMD_LOGOUT:
-		return "QQ_CMD_LOGOUT";
-	case QQ_CMD_KEEP_ALIVE:
-		return "QQ_CMD_KEEP_ALIVE";
-	case QQ_CMD_UPDATE_INFO:
-		return "QQ_CMD_UPDATE_INFO";
-	case QQ_CMD_SEARCH_USER:
-		return "QQ_CMD_SEARCH_USER";
-	case QQ_CMD_GET_BUDDY_INFO:
-		return "QQ_CMD_GET_BUDDY_INFO";
-	case QQ_CMD_ADD_BUDDY_WO_AUTH:
-		return "QQ_CMD_ADD_BUDDY_WO_AUTH";
-	case QQ_CMD_DEL_BUDDY:
-		return "QQ_CMD_DEL_BUDDY";
-	case QQ_CMD_BUDDY_AUTH:
-		return "QQ_CMD_BUDDY_AUTH";
-	case QQ_CMD_CHANGE_STATUS:
-		return "QQ_CMD_CHANGE_STATUS";
-	case QQ_CMD_ACK_SYS_MSG:
-		return "QQ_CMD_ACK_SYS_MSG";
-	case QQ_CMD_SEND_IM:
-		return "QQ_CMD_SEND_IM";
-	case QQ_CMD_RECV_IM:
-		return "QQ_CMD_RECV_IM";
-	case QQ_CMD_REMOVE_SELF:
-		return "QQ_CMD_REMOVE_SELF";
-	case QQ_CMD_LOGIN:
-		return "QQ_CMD_LOGIN";
-	case QQ_CMD_GET_BUDDIES_LIST:
-		return "QQ_CMD_GET_BUDDIES_LIST";
-	case QQ_CMD_GET_BUDDIES_ONLINE:
-		return "QQ_CMD_GET_BUDDIES_ONLINE";
-	case QQ_CMD_ROOM:
-		return "QQ_CMD_ROOM";
-	case QQ_CMD_GET_BUDDIES_AND_ROOMS:
-		return "QQ_CMD_GET_BUDDIES_AND_ROOMS";
-	case QQ_CMD_GET_LEVEL:
-		return "QQ_CMD_GET_LEVEL";
-	case QQ_CMD_TOKEN:
-		return "QQ_CMD_TOKEN";
-	case QQ_CMD_RECV_MSG_SYS:
-		return "QQ_CMD_RECV_MSG_SYS";
-	case QQ_CMD_BUDDY_CHANGE_STATUS:
-		return "QQ_CMD_BUDDY_CHANGE_STATUS";
-	default:
-		return "Unknown CMD";
-	}
-}
-
-const gchar *qq_get_room_cmd_desc(gint room_cmd)
-{
-	switch (room_cmd) {
-	case QQ_ROOM_CMD_CREATE:
-		return "QQ_ROOM_CMD_CREATE";
-	case QQ_ROOM_CMD_MEMBER_OPT:
-		return "QQ_ROOM_CMD_MEMBER_OPT";
-	case QQ_ROOM_CMD_CHANGE_INFO:
-		return "QQ_ROOM_CMD_CHANGE_INFO";
-	case QQ_ROOM_CMD_GET_INFO:
-		return "QQ_ROOM_CMD_GET_INFO";
-	case QQ_ROOM_CMD_ACTIVATE:
-		return "QQ_ROOM_CMD_ACTIVATE";
-	case QQ_ROOM_CMD_SEARCH:
-		return "QQ_ROOM_CMD_SEARCH";
-	case QQ_ROOM_CMD_JOIN:
-		return "QQ_ROOM_CMD_JOIN";
-	case QQ_ROOM_CMD_AUTH:
-		return "QQ_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";
-	case QQ_ROOM_CMD_GET_ONLINES:
-		return "QQ_ROOM_CMD_GET_ONLINES";
-	case QQ_ROOM_CMD_GET_BUDDIES:
-		return "QQ_ROOM_CMD_GET_BUDDIES";
-	case QQ_ROOM_CMD_CHANGE_CARD:
-		return "QQ_ROOM_CMD_CHANGE_CARD";
-	case QQ_ROOM_CMD_GET_REALNAMES:
-		return "QQ_ROOM_CMD_GET_REALNAMES";
-	case QQ_ROOM_CMD_GET_CARD:
-		return "QQ_ROOM_CMD_GET_CARD";
-	case QQ_ROOM_CMD_SEND_IM_EX:
-		return "QQ_ROOM_CMD_SEND_IM_EX";
-	case QQ_ROOM_CMD_ADMIN:
-		return "QQ_ROOM_CMD_ADMIN";
-	case QQ_ROOM_CMD_TRANSFER:
-		return "QQ_ROOM_CMD_TRANSFER";
-	case QQ_ROOM_CMD_TEMP_CREATE:
-		return "QQ_ROOM_CMD_TEMP_CREATE";
-	case QQ_ROOM_CMD_TEMP_CHANGE_MEMBER:
-		return "QQ_ROOM_CMD_TEMP_CHANGE_MEMBER";
-	case QQ_ROOM_CMD_TEMP_QUIT:
-		return "QQ_ROOM_CMD_TEMP_QUIT";
-	case QQ_ROOM_CMD_TEMP_GET_INFO:
-		return "QQ_ROOM_CMD_TEMP_GET_INFO";
-	case QQ_ROOM_CMD_TEMP_SEND_IM:
-		return "QQ_ROOM_CMD_TEMP_SEND_IM";
-	case QQ_ROOM_CMD_TEMP_GET_MEMBERS:
-		return "QQ_ROOM_CMD_TEMP_GET_MEMBERS";
-	default:
-		return "Unknown Room Command";
-	}
-}
--- a/libpurple/protocols/qq/header_info.h	Thu Nov 06 02:21:16 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,101 +0,0 @@
-/**
- * @file header_info.h
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#ifndef _QQ_HEADER_INFO_H_
-#define _QQ_HEADER_INFO_H_
-
-#include <glib.h>
-
-#define QQ_UDP_HEADER_LENGTH    7
-#define QQ_TCP_HEADER_LENGTH    9
-
-#define QQ_PACKET_TAG           0x02	/* all QQ text packets starts with it */
-#define QQ_PACKET_TAIL          0x03	/* all QQ text packets end with it */
-
-#define QQ_CLIENT       0x0d55
-
-const gchar *qq_get_ver_desc(gint source);
-
-/* list of known QQ commands */
-enum {
-	QQ_CMD_LOGOUT = 0x0001,				/* log out */
-	QQ_CMD_KEEP_ALIVE = 0x0002,			/* get onlines from tencent */
-	QQ_CMD_UPDATE_INFO = 0x0004,			/* update information */
-	QQ_CMD_SEARCH_USER = 0x0005,			/* search for user */
-	QQ_CMD_GET_BUDDY_INFO = 0x0006,			/* get user information */
-	QQ_CMD_ADD_BUDDY_WO_AUTH = 0x0009,		/* add buddy without auth */
-	QQ_CMD_DEL_BUDDY = 0x000a,			/* delete a buddy  */
-	QQ_CMD_BUDDY_AUTH = 0x000b,			/* buddy authentication */
-	QQ_CMD_CHANGE_STATUS = 0x000d,		/* change my online status */
-	QQ_CMD_ACK_SYS_MSG = 0x0012,			/* ack system message */
-	QQ_CMD_SEND_IM = 0x0016,			/* send message */
-	QQ_CMD_RECV_IM = 0x0017,			/* receive message */
-	QQ_CMD_REMOVE_SELF = 0x001c,			/* remove self */
-	QQ_CMD_REQUEST_KEY = 0x001d,			/* request key for file transfer */
-	QQ_CMD_CELL_PHONE_1 = 0x0021,			/* cell phone 1 */
-	QQ_CMD_LOGIN = 0x0022,				/* login */
-	QQ_CMD_GET_BUDDIES_LIST = 0x0026,		/* get buddies list */
-	QQ_CMD_GET_BUDDIES_ONLINE = 0x0027,		/* get online buddies list */
-	QQ_CMD_CELL_PHONE_2 = 0x0029,			/* cell phone 2 */
-	QQ_CMD_ROOM = 0x0030,			/* room command */
-	QQ_CMD_GET_BUDDIES_AND_ROOMS = 0x0058,
-	QQ_CMD_GET_LEVEL = 0x005C,			/* get level for one or more buddies */
-	QQ_CMD_TOKEN  = 0x0062, 		/* get login token */
-	QQ_CMD_RECV_MSG_SYS = 0x0080,			/* receive a system message */
-	QQ_CMD_BUDDY_CHANGE_STATUS = 0x0081,	/* buddy change status */
-};
-
-const gchar *qq_get_cmd_desc(gint type);
-
-enum {
-	QQ_ROOM_CMD_CREATE = 0x01,
-	QQ_ROOM_CMD_MEMBER_OPT = 0x02,
-	QQ_ROOM_CMD_CHANGE_INFO = 0x03,
-	QQ_ROOM_CMD_GET_INFO = 0x04,
-	QQ_ROOM_CMD_ACTIVATE = 0x05,
-	QQ_ROOM_CMD_SEARCH = 0x06,
-	QQ_ROOM_CMD_JOIN = 0x07,
-	QQ_ROOM_CMD_AUTH = 0x08,
-	QQ_ROOM_CMD_QUIT = 0x09,
-	QQ_ROOM_CMD_SEND_MSG = 0x0a,
-	QQ_ROOM_CMD_GET_ONLINES = 0x0b,
-	QQ_ROOM_CMD_GET_BUDDIES = 0x0c,
-
-	QQ_ROOM_CMD_CHANGE_CARD = 0x0E,
-	QQ_ROOM_CMD_GET_REALNAMES = 0x0F,
-	QQ_ROOM_CMD_GET_CARD = 0x10,
-	QQ_ROOM_CMD_SEND_IM_EX = 0x1A,
-	QQ_ROOM_CMD_ADMIN = 0x1B,
-	QQ_ROOM_CMD_TRANSFER = 0x1C,
-	QQ_ROOM_CMD_TEMP_CREATE = 0x30,
-	QQ_ROOM_CMD_TEMP_CHANGE_MEMBER = 0x31,
-	QQ_ROOM_CMD_TEMP_QUIT = 0x32,
-	QQ_ROOM_CMD_TEMP_GET_INFO = 0x33,
-	QQ_ROOM_CMD_TEMP_SEND_IM = 0x35,
-	QQ_ROOM_CMD_TEMP_GET_MEMBERS = 0x37,
-};
-
-const gchar *qq_get_room_cmd_desc(gint room_cmd);
-
-#endif
--- a/libpurple/protocols/qq/im.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/im.c	Thu Nov 06 03:20:05 2008 +0000
@@ -35,15 +35,13 @@
 #include "buddy_list.h"
 #include "buddy_opt.h"
 #include "char_conv.h"
-#include "group_im.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "im.h"
 #include "packet_parse.h"
 #include "qq_network.h"
 #include "send_file.h"
 #include "utils.h"
 
-#define QQ_SEND_IM_REPLY_OK       0x00
 #define DEFAULT_FONT_NAME_LEN 	  4
 
 enum
@@ -64,52 +62,15 @@
 	QQ_NORMAL_IM_FILE_EX_NOTIFY_IP = 0x87
 };
 
-enum {
-	QQ_RECV_SYS_IM_KICK_OUT = 0x01
-};
-
-typedef struct _qq_recv_im_header qq_recv_im_header;
-typedef struct _qq_recv_normal_im_text qq_recv_normal_im_text;
-typedef struct _qq_recv_normal_im_common qq_recv_normal_im_common;
-typedef struct _qq_recv_normal_im_unprocessed qq_recv_normal_im_unprocessed;
-
-struct _qq_recv_normal_im_common {
-	/* this is the common part of normal_text */
-	guint16 sender_ver;
-	guint32 sender_uid;
-	guint32 receiver_uid;
-	guint8 session_md5[QQ_KEY_LENGTH];
-	guint16 normal_im_type;
-};
+typedef struct _qq_im_header qq_im_header;
+typedef struct _qq_recv_extended_im_text qq_recv_extended_im_text;
 
-struct _qq_recv_normal_im_text {
-	qq_recv_normal_im_common *common;
-	/* 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 msg_type;
-	gchar *msg;		/* no fixed length, ends with 0x00 */
-	guint8 *font_attr;
-	gint font_attr_len;
-};
-
-struct _qq_recv_normal_im_unprocessed {
-	qq_recv_normal_im_common *common;
-	/* now comes the part of unprocessed */
-	guint8 *unknown;	/* no fixed length */
-	gint length;
-};
-
-struct _qq_recv_im_header {
-	guint32 sender_uid;
-	guint32 receiver_uid;
-	guint32 server_im_seq;
-	struct in_addr sender_ip;
-	guint16 sender_port;
+struct _qq_im_header {
+	/* this is the common part of normal_text */
+	guint16 version_from;
+	guint32 uid_from;
+	guint32 uid_to;
+	guint8 session_md5[QQ_KEY_LENGTH];
 	guint16 im_type;
 };
 
@@ -182,327 +143,360 @@
 	return (guint8 *) send_im_tail;
 }
 
-static const gchar *qq_get_recv_im_type_str(gint type)
-{
-	switch (type) {
-		case QQ_RECV_IM_TO_BUDDY:
-			return "QQ_RECV_IM_TO_BUDDY";
-		case QQ_RECV_IM_TO_UNKNOWN:
-			return "QQ_RECV_IM_TO_UNKNOWN";
-		case QQ_RECV_IM_UNKNOWN_QUN_IM:
-			return "QQ_RECV_IM_UNKNOWN_QUN_IM";
-		case QQ_RECV_IM_ADD_TO_QUN:
-			return "QQ_RECV_IM_ADD_TO_QUN";
-		case QQ_RECV_IM_DEL_FROM_QUN:
-			return "QQ_RECV_IM_DEL_FROM_QUN";
-		case QQ_RECV_IM_APPLY_ADD_TO_QUN:
-			return "QQ_RECV_IM_APPLY_ADD_TO_QUN";
-		case QQ_RECV_IM_CREATE_QUN:
-			return "QQ_RECV_IM_CREATE_QUN";
-		case QQ_RECV_IM_SYS_NOTIFICATION:
-			return "QQ_RECV_IM_SYS_NOTIFICATION";
-		case QQ_RECV_IM_APPROVE_APPLY_ADD_TO_QUN:
-			return "QQ_RECV_IM_APPROVE_APPLY_ADD_TO_QUN";
-		case QQ_RECV_IM_REJCT_APPLY_ADD_TO_QUN:
-			return "QQ_RECV_IM_REJCT_APPLY_ADD_TO_QUN";
-		case QQ_RECV_IM_TEMP_QUN_IM:
-			return "QQ_RECV_IM_TEMP_QUN_IM";
-		case QQ_RECV_IM_QUN_IM:
-			return "QQ_RECV_IM_QUN_IM";
-		case QQ_RECV_IM_NEWS:
-			return "QQ_RECV_IM_NEWS";
-		case QQ_RECV_IM_FROM_BUDDY_2006:
-			return "QQ_RECV_IM_FROM_BUDDY_2006";
-		case QQ_RECV_IM_FROM_UNKNOWN_2006:
-			return "QQ_RECV_IM_FROM_UNKNOWN_2006";
-		default:
-			return "QQ_RECV_IM_UNKNOWN";
-	}
-}
-
 /* read the common parts of the normal_im,
  * returns the bytes read if succeed, or -1 if there is any error */
-static gint _qq_normal_im_common_read(guint8 *data, gint len, qq_recv_normal_im_common *common)
+static gint get_im_header(qq_im_header *im_header, guint8 *data, gint len)
 {
 	gint bytes;
-	g_return_val_if_fail(data != NULL && len != 0 && common != NULL, -1);
+	g_return_val_if_fail(data != NULL && len > 0, -1);
 
 	bytes = 0;
-	/* now push data into common header */
-	bytes += qq_get16(&(common->sender_ver), data + bytes);
-	bytes += qq_get32(&(common->sender_uid), data + bytes);
-	bytes += qq_get32(&(common->receiver_uid), data + bytes);
-	bytes += qq_getdata(common->session_md5, QQ_KEY_LENGTH, data + bytes);
-	bytes += qq_get16(&(common->normal_im_type), data + bytes);
-
-	if (bytes != 28) {	/* read common place fail */
-		purple_debug_error("QQ", "Expect 28 bytes, read %d bytes\n", bytes);
-		return -1;
-	}
-
+	bytes += qq_get16(&(im_header->version_from), data + bytes);
+	bytes += qq_get32(&(im_header->uid_from), data + bytes);
+	bytes += qq_get32(&(im_header->uid_to), data + bytes);
+	bytes += qq_getdata(im_header->session_md5, QQ_KEY_LENGTH, data + bytes);
+	bytes += qq_get16(&(im_header->im_type), data + bytes);
 	return bytes;
 }
 
-static void _qq_process_recv_news(guint8 *data, gint data_len, PurpleConnection *gc)
+void qq_got_attention(PurpleConnection *gc, const gchar *msg)
 {
-	qq_data *qd = (qq_data *) gc->proto_data;
-	gint bytes;
-	guint8 *temp;
-	guint8 temp_len;
-	gchar *title, *brief, *url;
-	gchar *title_utf8;
-	gchar *content, *content_utf8;
-
-	g_return_if_fail(data != NULL && data_len != 0);
-
-#if 0
-	qq_show_packet("Rcv news", data, data_len);
-#endif
-
-	temp = g_newa(guint8, data_len);
-	bytes = 4;	/* ignore unknown 4 bytes */
-
-	bytes += qq_get8(&temp_len, data + bytes);
-	g_return_if_fail(bytes + temp_len <= data_len);
-	bytes += qq_getdata(temp, temp_len, data+bytes);
-	title = g_strndup((gchar *)temp, temp_len);
+	qq_data *qd;
+	gchar *from;
+	time_t now = time(NULL);
 
-	bytes += qq_get8(&temp_len, data + bytes);
-	g_return_if_fail(bytes + temp_len <= data_len);
-	bytes += qq_getdata(temp, temp_len, data+bytes);
-	brief = g_strndup((gchar *)temp, temp_len);
+	g_return_if_fail(gc != NULL  && gc->proto_data != NULL);
+	qd = gc->proto_data;
 
-	bytes += qq_get8(&temp_len, data + bytes);
-	g_return_if_fail(bytes + temp_len <= data_len);
-	bytes += qq_getdata(temp, temp_len, data+bytes);
-	url = g_strndup((gchar *)temp, temp_len);
+	g_return_if_fail(qd->uid > 0);
 
-	title_utf8 = qq_to_utf8(title, QQ_CHARSET_DEFAULT);
-	content = g_strdup_printf(_("%s\n\n%s"), brief, url);
-	content_utf8 = qq_to_utf8(content, QQ_CHARSET_DEFAULT);
+	qq_buddy_find_or_new(gc, qd->uid);
 
-	if (qd->is_show_news) {
-		purple_notify_info(gc, _("QQ Server News"), title_utf8, content_utf8);
-	} else {
-		purple_debug_info("QQ", "QQ Server news:\n%s\n%s", title_utf8, content_utf8);
-	}
-	g_free(title);
-	g_free(title_utf8);
-	g_free(brief);
-	g_free(url);
-	g_free(content);
-	g_free(content_utf8);
+	from = uid_to_purple_name(qd->uid);
+	serv_got_im(gc, from, msg, PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NOTIFY, now);
+	g_free(from);
 }
 
 /* process received normal text IM */
-static void _qq_process_recv_normal_im_text(guint8 *data, gint len, qq_recv_normal_im_common *common, PurpleConnection *gc)
+static void process_im_text(PurpleConnection *gc, guint8 *data, gint len, qq_im_header *im_header)
 {
 	guint16 purple_msg_type;
-	gchar *name;
+	gchar *who;
 	gchar *msg_with_purple_smiley;
 	gchar *msg_utf8_encoded;
 	qq_data *qd;
-	qq_recv_normal_im_text *im_text;
 	gint bytes = 0;
 	PurpleBuddy *b;
-	qq_buddy *qq_b;
-
-	g_return_if_fail(common != NULL);
-	qd = (qq_data *) gc->proto_data;
+	qq_buddy_data *bd;
 
-	/* now it is QQ_NORMAL_IM_TEXT */
-	/*
-	   if (*cursor >= (data + len - 1)) {
-	   purple_debug_warning("QQ", "Received normal IM text is empty\n");
-	   return;
-	   } else
-	   */
-	im_text = g_newa(qq_recv_normal_im_text, 1);
+	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 msg_type;
+		gchar *msg;		/* no fixed length, ends with 0x00 */
+		guint8 *font_attr;
+		gint font_attr_len;
+	} im_text;
 
-	im_text->common = common;
+	g_return_if_fail (data != NULL && len > 0);
+	g_return_if_fail(im_header != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	memset(&im_text, 0, sizeof(im_text));
 
 	/* push data into im_text */
-	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);
+	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_get8(&(im_text->msg_type), data + bytes);
+	bytes += qq_getdata((guint8 *) & (im_text.unknown3), 4, data + bytes);
+	bytes += qq_get8(&(im_text.msg_type), data + bytes);
 
 	/* 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 */
-		im_text->msg = g_strndup((gchar *)(data + bytes), len - bytes);
+	if (im_text.msg_type == QQ_IM_AUTO_REPLY) {
+		im_text.is_there_font_attr = 0x00;	/* indeed there is no this flag */
+		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 */
+		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 */
 
-	name = uid_to_purple_name(common->sender_uid);
-	b = purple_find_buddy(gc->account, name);
+	who = uid_to_purple_name(im_header->uid_from);
+	b = purple_find_buddy(gc->account, who);
 	if (b == NULL) {
-		qq_add_buddy_by_recv_packet(gc, common->sender_uid, FALSE, TRUE);
-		b = purple_find_buddy(gc->account, name);
+		/* create no-auth buddy */
+		b = qq_buddy_new(gc, im_header->uid_from);
 	}
-	qq_b = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
-	if (qq_b != NULL) {
-		qq_b->client_version = common->sender_ver;
+	bd = (b == NULL) ? NULL : (qq_buddy_data *) b->proto_data;
+	if (bd != NULL) {
+		bd->client_tag = im_header->version_from;
 	}
 
-	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) : qq_to_utf8(msg_with_purple_smiley, QQ_CHARSET_DEFAULT);
+	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);
 
-	/* send encoded to purple, note that we use im_text->send_time,
+	/* 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, name, msg_utf8_encoded, purple_msg_type, (time_t) im_text->send_time);
+	serv_got_im(gc, who, msg_utf8_encoded, purple_msg_type, (time_t) im_text.send_time);
 
 	g_free(msg_utf8_encoded);
 	g_free(msg_with_purple_smiley);
-	g_free(name);
-	g_free(im_text->msg);
-	if (im_text->is_there_font_attr)
-		g_free(im_text->font_attr);
+	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)
+{
+	guint16 purple_msg_type;
+	gchar *who;
+	gchar *msg_with_purple_smiley;
+	gchar *msg_utf8_encoded;
+	qq_data *qd;
+	PurpleBuddy *b;
+	qq_buddy_data *bd;
+	gint bytes, text_len;
+
+	struct {
+		/* now comes the part for text only */
+		guint16 sessionId;
+		guint32 send_time;
+		guint16 senderHead;
+		guint32 flag;
+		guint8 unknown2[8];
+		guint8 fragmentCount;
+		guint8 fragmentIndex;
+		guint16 messageId;
+		guint8 replyType;
+		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);
+	g_return_if_fail(im_header != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	memset(&im_text, 0, sizeof(im_text));
+
+	/* push data into im_text */
+	bytes = 0;
+	bytes += qq_get16(&(im_text.sessionId), 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;
+
+	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.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;
+	}
+
+	who = uid_to_purple_name(im_header->uid_from);
+	b = purple_find_buddy(gc->account, who);
+	if (b == NULL) {
+		/* create no-auth buddy */
+		b = qq_buddy_new(gc, im_header->uid_from);
+	}
+	bd = (b == NULL) ? NULL : (qq_buddy_data *) b->proto_data;
+	if (bd != NULL) {
+		bd->client_tag = im_header->version_from;
+	}
+
+	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);
+
+	/* 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);
+
+	g_free(msg_utf8_encoded);
+	g_free(msg_with_purple_smiley);
+	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 */
-static void _qq_process_recv_normal_im(guint8 *data, gint len, PurpleConnection *gc)
+void qq_process_im(PurpleConnection *gc, guint8 *data, gint len)
 {
 	gint bytes = 0;
-	qq_recv_normal_im_common *common;
-	qq_recv_normal_im_unprocessed *im_unprocessed;
+	qq_im_header im_header;
 
-	g_return_if_fail (data != NULL && len != 0);
+	g_return_if_fail (data != NULL && len > 0);
 
-	common = g_newa (qq_recv_normal_im_common, 1);
-
-	bytes = _qq_normal_im_common_read(data, len, common);
+	bytes = get_im_header(&im_header, data, len);
 	if (bytes < 0) {
-		purple_debug_error("QQ", "Fail read the common part of normal IM\n");
+		purple_debug_error("QQ", "Fail read im header, len %d\n", len);
+		qq_show_packet ("IM Header", data, len);
 		return;
 	}
+	purple_debug_info("QQ",
+			"Got IM to %d, type: %02X from: %d 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 (common->normal_im_type) {
+	switch (im_header.im_type) {
 		case QQ_NORMAL_IM_TEXT:
-			purple_debug_info("QQ",
-					"Normal IM, text type:\n [%d] => [%d], src: %s (%04X)\n",
-					common->sender_uid, common->receiver_uid,
-					qq_get_ver_desc (common->sender_ver), common->sender_ver);
 			if (bytes >= len - 1) {
 				purple_debug_warning("QQ", "Received normal IM text is empty\n");
 				return;
 			}
-			_qq_process_recv_normal_im_text(data + bytes, len - bytes, common, gc);
+			process_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, common->sender_uid, gc);
+			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, common->sender_uid, gc);
+			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, common->sender_uid, gc);
+			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, common->sender_uid, gc);
+			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, common->sender_uid, gc);
+			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*/
-			purple_debug_warning("QQ", "Normal IM, not support QQ_NORMAL_IM_FILE_REQUEST_TCP\n");
-			qq_show_packet ("Not support", data, len);
-			break;
 		case QQ_NORMAL_IM_FILE_APPROVE_TCP:
-			purple_debug_warning("QQ", "Normal IM, not support QQ_NORMAL_IM_FILE_APPROVE_TCP\n");
-			qq_show_packet ("Not support", data, len);
-			break;
 		case QQ_NORMAL_IM_FILE_REJECT_TCP:
-			purple_debug_warning("QQ", "Normal IM, not support QQ_NORMAL_IM_FILE_REJECT_TCP\n");
-			qq_show_packet ("Not support", data, len);
-			break;
 		case QQ_NORMAL_IM_FILE_PASV:
-			purple_debug_warning("QQ", "Normal IM, not support QQ_NORMAL_IM_FILE_PASV\n");
-			qq_show_packet ("Not support", data, len);
-			break;
 		case QQ_NORMAL_IM_FILE_EX_REQUEST_UDP:
-			purple_debug_warning("QQ", "Normal IM, not support QQ_NORMAL_IM_FILE_REQUEST_TCP\n");
-			qq_show_packet ("QQ", data, len);
-			break;
 		case QQ_NORMAL_IM_FILE_EX_REQUEST_ACCEPT:
-			purple_debug_warning("QQ", "Normal IM, not support QQ_NORMAL_IM_FILE_EX_REQUEST_ACCEPT\n");
-			qq_show_packet ("QQ", data, len);
-			break;
 		case QQ_NORMAL_IM_FILE_EX_REQUEST_CANCEL:
-			purple_debug_warning("QQ", "Normal IM, not support QQ_NORMAL_IM_FILE_EX_REQUEST_CANCEL\n");
-			qq_show_packet ("Not support", data, len);
-			break;
 		case QQ_NORMAL_IM_FILE_EX_NOTIFY_IP:
-			purple_debug_warning("QQ", "Normal IM, not support QQ_NORMAL_IM_FILE_EX_NOTIFY_IP\n");
 			qq_show_packet ("Not support", data, len);
 			break;
 		default:
-			im_unprocessed = g_newa (qq_recv_normal_im_unprocessed, 1);
-			im_unprocessed->common = common;
-			im_unprocessed->unknown = data + bytes;
-			im_unprocessed->length = len - bytes;
 			/* a simple process here, maybe more later */
-			purple_debug_warning("QQ",
-					"Normal IM, unprocessed type [0x%04x], len %d\n",
-					common->normal_im_type, im_unprocessed->length);
-			qq_show_packet ("QQ", im_unprocessed->unknown, im_unprocessed->length);
+			qq_show_packet ("Unknow", data + bytes, len - bytes);
 			return;
 	}
 }
 
-/* process im from system administrator */
-static void _qq_process_recv_sys_im(guint8 *data, gint data_len, PurpleConnection *gc)
+/* it is a extended IM, maybe text or video request */
+void qq_process_extend_im(PurpleConnection *gc, guint8 *data, gint len)
 {
-	gint len;
-	guint8 reply;
-	gchar **segments, *msg_utf8;
+	gint bytes;
+	qq_im_header im_header;
+
+	g_return_if_fail (data != NULL && len > 0);
 
-	g_return_if_fail(data != NULL && data_len != 0);
+	bytes = get_im_header(&im_header, data, len);
+	if (bytes < 0) {
+		purple_debug_error("QQ", "Fail read im header, len %d\n", len);
+		qq_show_packet ("IM Header", data, len);
+		return;
+	}
+	purple_debug_info("QQ",
+			"Got Extend IM to %d, type: %02X from: %d 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);
 
-	len = data_len;
-
-	if (NULL == (segments = split_data(data, len, "\x2f", 2)))
-		return;
-
-	reply = strtol(segments[0], NULL, 10);
-	if (reply == QQ_RECV_SYS_IM_KICK_OUT)
-		purple_debug_warning("QQ", "We are kicked out by QQ server\n");
-	msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT);
-	purple_notify_warning(gc, NULL, _("System Message"), msg_utf8);
+	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;
+	}
 }
 
-/* send an IM to to_uid */
-void qq_send_packet_im(PurpleConnection *gc, guint32 to_uid, gchar *msg, gint type)
+/* send an IM to uid_to */
+void qq_request_send_im(PurpleConnection *gc, guint32 uid_to, gchar *msg, gint type)
 {
 	qq_data *qd;
 	guint8 *raw_data, *send_im_tail;
-	guint16 client_tag, normal_im_type;
+	guint16 im_type;
 	gint msg_len, raw_len, font_name_len, tail_len, bytes;
 	time_t now;
 	gchar *msg_filtered;
@@ -512,8 +506,7 @@
 	const gchar *start, *end, *last;
 
 	qd = (qq_data *) gc->proto_data;
-	client_tag = QQ_CLIENT;
-	normal_im_type = QQ_NORMAL_IM_TEXT;
+	im_type = QQ_NORMAL_IM_TEXT;
 
 	last = msg;
 	while (purple_markup_find_tag("font", last, &start, &end, &attribs)) {
@@ -570,17 +563,17 @@
 	/* 000-003: receiver uid */
 	bytes += qq_put32(raw_data + bytes, qd->uid);
 	/* 004-007: sender uid */
-	bytes += qq_put32(raw_data + bytes, to_uid);
+	bytes += qq_put32(raw_data + bytes, uid_to);
 	/* 008-009: sender client version */
-	bytes += qq_put16(raw_data + bytes, client_tag);
+	bytes += qq_put16(raw_data + bytes, qd->client_tag);
 	/* 010-013: receiver uid */
 	bytes += qq_put32(raw_data + bytes, qd->uid);
 	/* 014-017: sender uid */
-	bytes += qq_put32(raw_data + bytes, to_uid);
+	bytes += qq_put32(raw_data + bytes, uid_to);
 	/* 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, normal_im_type);
+	bytes += qq_put16(raw_data + bytes, im_type);
 	/* 036-037: sequence number */
 	bytes += qq_put16(raw_data + bytes, qd->send_seq);
 	/* 038-041: send time */
@@ -600,10 +593,10 @@
 	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_send_im_tail debug", send_im_tail, 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);
 
-	qq_show_packet("QQ_raw_data debug", raw_data, bytes);
+	/* qq_show_packet("QQ_CMD_SEND_IM, raw_data, bytes); */
 
 	if (bytes == raw_len)	/* create packet OK */
 		qq_send_cmd(gc, QQ_CMD_SEND_IM, raw_data, bytes);
@@ -619,127 +612,5 @@
 	g_free(msg_filtered);
 }
 
-/* parse the reply to send_im */
-void qq_process_send_im_reply(guint8 *data, gint data_len, PurpleConnection *gc)
-{
-	qq_data *qd;
-
-	g_return_if_fail(data != NULL && data_len != 0);
-
-	qd = gc->proto_data;
-
-	if (data[0] != QQ_SEND_IM_REPLY_OK) {
-		purple_debug_warning("QQ", "Send IM fail\n");
-		purple_notify_error(gc, _("Error"), _("Failed to send IM."), NULL);
-	}	else {
-		purple_debug_info("QQ", "IM ACK OK\n");
-	}
-}
-
-/* I receive a message, mainly it is text msg,
- * but we need to proess other types (group etc) */
-void qq_process_recv_im(guint8 *data, gint data_len, guint16 seq, PurpleConnection *gc)
-{
-	qq_data *qd;
-	gint bytes;
-	qq_recv_im_header *im_header;
-
-	g_return_if_fail(data != NULL && data_len != 0);
-
-	qd = (qq_data *) gc->proto_data;
-
-	if (data_len < 16) {	/* we need to ack with the first 16 bytes */
-		purple_debug_error("QQ", "MSG is too short\n");
-		return;
-	} else {
-		/* when we receive a message,
-		 * we send an ACK which is the first 16 bytes of incoming packet */
-		qq_send_server_reply(gc, QQ_CMD_RECV_IM, seq, data, 16);
-	}
-
-	/* check len first */
-	if (data_len < 20) {	/* length of im_header */
-		purple_debug_error("QQ", "Invald MSG header, len %d < 20\n", data_len);
-		return;
-	}
-
-	bytes = 0;
-	im_header = g_newa(qq_recv_im_header, 1);
-	bytes += qq_get32(&(im_header->sender_uid), data + bytes);
-	bytes += qq_get32(&(im_header->receiver_uid), data + bytes);
-	bytes += qq_get32(&(im_header->server_im_seq), data + bytes);
-	/* if the message is delivered via server, it is server IP/port */
-	bytes += qq_getIP(&(im_header->sender_ip), data + bytes);
-	bytes += qq_get16(&(im_header->sender_port), data + bytes);
-	bytes += qq_get16(&(im_header->im_type), data + bytes);
-	/* im_header prepared */
-
-	if (im_header->receiver_uid != qd->uid) {	/* should not happen */
-		purple_debug_error("QQ", "MSG to [%d], NOT me\n", im_header->receiver_uid);
-		return;
-	}
 
-	/* check bytes */
-	if (bytes >= data_len - 1) {
-		purple_debug_warning("QQ", "Empty MSG\n");
-		return;
-	}
 
-	switch (im_header->im_type) {
-		case QQ_RECV_IM_NEWS:
-			_qq_process_recv_news(data + bytes, data_len - bytes, gc);
-			break;
-		case QQ_RECV_IM_FROM_BUDDY_2006:
-		case QQ_RECV_IM_FROM_UNKNOWN_2006:
-		case QQ_RECV_IM_TO_UNKNOWN:
-		case QQ_RECV_IM_TO_BUDDY:
-			purple_debug_info("QQ", "MSG from buddy [%d]\n", im_header->sender_uid);
-			_qq_process_recv_normal_im(data + bytes, data_len - bytes, gc);
-			break;
-		case QQ_RECV_IM_UNKNOWN_QUN_IM:
-		case QQ_RECV_IM_TEMP_QUN_IM:
-		case QQ_RECV_IM_QUN_IM:
-			purple_debug_info("QQ", "MSG from room [%d]\n", im_header->sender_uid);
-			/* sender_uid is in fact id */
-			qq_process_room_msg_normal(data + bytes, data_len - bytes, im_header->sender_uid, gc, im_header->im_type);
-			break;
-		case QQ_RECV_IM_ADD_TO_QUN:
-			purple_debug_info("QQ", "Notice from [%d], Added\n", im_header->sender_uid);
-			/* sender_uid is group id
-			 * we need this to create a dummy group and add to blist */
-			qq_process_room_msg_been_added(data + bytes, data_len - bytes, im_header->sender_uid, gc);
-			break;
-		case QQ_RECV_IM_DEL_FROM_QUN:
-			purple_debug_info("QQ", "Notice from room [%d], Removed\n", im_header->sender_uid);
-			/* sender_uid is group id */
-			qq_process_room_msg_been_removed(data + bytes, data_len - bytes, im_header->sender_uid, gc);
-			break;
-		case QQ_RECV_IM_APPLY_ADD_TO_QUN:
-			purple_debug_info("QQ", "Notice from room [%d], Joined\n", im_header->sender_uid);
-			/* sender_uid is group id */
-			qq_process_room_msg_apply_join(data + bytes, data_len - bytes, im_header->sender_uid, gc);
-			break;
-		case QQ_RECV_IM_APPROVE_APPLY_ADD_TO_QUN:
-			purple_debug_info("QQ", "Notice from room [%d], Confirm add in\n",
-					im_header->sender_uid);
-			/* sender_uid is group id */
-			qq_process_room_msg_been_approved(data + bytes, data_len - bytes, im_header->sender_uid, gc);
-			break;
-		case QQ_RECV_IM_REJCT_APPLY_ADD_TO_QUN:
-			purple_debug_info("QQ", "Notice from room [%d], Refuse add in\n",
-					im_header->sender_uid);
-			/* sender_uid is group id */
-			qq_process_room_msg_been_rejected(data + bytes, data_len - bytes, im_header->sender_uid, gc);
-			break;
-		case QQ_RECV_IM_SYS_NOTIFICATION:
-			purple_debug_info("QQ", "Admin notice from [%d]\n", im_header->sender_uid);
-			_qq_process_recv_sys_im(data + bytes, data_len - bytes, gc);
-			break;
-		default:
-			purple_debug_warning("QQ", "MSG from [%d], unknown type %s [0x%02x]\n",
-					im_header->sender_uid, qq_get_recv_im_type_str(im_header->im_type),
-					im_header->im_type);
-			qq_show_packet("Unknown MSG type", data, data_len);
-	}
-}
-
--- a/libpurple/protocols/qq/im.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/im.h	Thu Nov 06 03:20:05 2008 +0000
@@ -39,30 +39,33 @@
 };
 
 enum {
-	QQ_RECV_IM_TO_BUDDY = 0x0009,
-	QQ_RECV_IM_TO_UNKNOWN = 0x000a,
-	QQ_RECV_IM_NEWS = 0x0018,
-	QQ_RECV_IM_UNKNOWN_QUN_IM = 0x0020,
-	QQ_RECV_IM_ADD_TO_QUN = 0x0021,
-	QQ_RECV_IM_DEL_FROM_QUN = 0x0022,
-	QQ_RECV_IM_APPLY_ADD_TO_QUN = 0x0023,
-	QQ_RECV_IM_APPROVE_APPLY_ADD_TO_QUN = 0x0024,
-	QQ_RECV_IM_REJCT_APPLY_ADD_TO_QUN = 0x0025,
-	QQ_RECV_IM_CREATE_QUN = 0x0026,
-	QQ_RECV_IM_TEMP_QUN_IM = 0x002A,
-	QQ_RECV_IM_QUN_IM = 0x002B,
-	QQ_RECV_IM_SYS_NOTIFICATION = 0x0030,
-	QQ_RECV_IM_FROM_BUDDY_2006 = 0x0084,
-	QQ_RECV_IM_FROM_UNKNOWN_2006 = 0x0085,
+	QQ_MSG_TO_BUDDY = 0x0009,
+	QQ_MSG_TO_UNKNOWN = 0x000a,
+	QQ_MSG_NEWS = 0x0018,
+	QQ_MSG_UNKNOWN_QUN_IM = 0x0020,
+	QQ_MSG_ADD_TO_QUN = 0x0021,
+	QQ_MSG_DEL_FROM_QUN = 0x0022,
+	QQ_MSG_APPLY_ADD_TO_QUN = 0x0023,
+	QQ_MSG_APPROVE_APPLY_ADD_TO_QUN = 0x0024,
+	QQ_MSG_REJCT_APPLY_ADD_TO_QUN = 0x0025,
+	QQ_MSG_CREATE_QUN = 0x0026,
+	QQ_MSG_TEMP_QUN_IM = 0x002A,
+	QQ_MSG_QUN_IM = 0x002B,
+	QQ_MSG_SYS_30 = 0x0030,
+	QQ_MSG_SYS_4C = 0x004C,
+	QQ_MSG_EXTEND = 0x0084,
+	QQ_MSG_EXTEND_85 = 0x0085,
 };
 
-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);
+void qq_got_attention(PurpleConnection *gc, const gchar *msg);
 
-void qq_send_packet_im(PurpleConnection *gc, guint32 to_uid, gchar *msg, gint type);
-void qq_process_recv_im(guint8 *data, gint data_len, guint16 seq, PurpleConnection *gc);
-void qq_process_send_im_reply(guint8 *data, gint data_len, PurpleConnection *gc);
+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);
 
+void qq_request_send_im(PurpleConnection *gc, guint32 uid_to, gchar *msg, gint type);
+
+void qq_process_im(PurpleConnection *gc, guint8 *data, gint len);
+void qq_process_extend_im(PurpleConnection *gc, guint8 *data, gint len);
 #endif
--- a/libpurple/protocols/qq/packet_parse.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/packet_parse.c	Thu Nov 06 03:20:05 2008 +0000
@@ -38,7 +38,7 @@
 #define PARSER_DEBUG
 #endif
 
-/* read one byte from buf, 
+/* read one byte from buf,
  * return the number of bytes read if succeeds, otherwise return -1 */
 gint qq_get8(guint8 *b, guint8 *buf)
 {
@@ -53,7 +53,7 @@
 }
 
 
-/* read two bytes as "guint16" from buf, 
+/* read two bytes as "guint16" from buf,
  * return the number of bytes read if succeeds, otherwise return -1 */
 gint qq_get16(guint16 *w, guint8 *buf)
 {
@@ -67,7 +67,7 @@
 	return sizeof(w_dest);
 }
 
-/* read four bytes as "guint32" from buf, 
+/* read four bytes as "guint32" from buf,
  * return the number of bytes read if succeeds, otherwise return -1 */
 gint qq_get32(guint32 *dw, guint8 *buf)
 {
@@ -87,7 +87,7 @@
 	return sizeof(struct in_addr);
 }
 
-/* read datalen bytes from buf, 
+/* read datalen bytes from buf,
  * return the number of bytes read if succeeds, otherwise return -1 */
 gint qq_getdata(guint8 *data, gint datalen, guint8 *buf)
 {
@@ -151,7 +151,20 @@
  * return the number of bytes packed, otherwise return -1 */
 gint qq_put32(guint8 *buf, guint32 dw)
 {
-    guint32 dw_porter;
+	guint32 dw_porter;
+    dw_porter = g_htonl(dw);
+#ifdef PARSER_DEBUG
+	purple_debug_info("QQ", "[DBG][put32] buf %p\n", (void *)buf);
+	purple_debug_info("QQ", "[DBG][put32] dw 0x%08x, dw_porter 0x%08x\n", dw, dw_porter);
+#endif
+    memcpy(buf, &dw_porter, sizeof(dw_porter));
+    return sizeof(dw_porter);
+}
+
+gint qq_putime(guint8 *buf, time_t *t)
+{
+	guint32 dw, dw_porter;
+	memcpy(&dw, t, sizeof(dw));
     dw_porter = g_htonl(dw);
 #ifdef PARSER_DEBUG
 	purple_debug_info("QQ", "[DBG][put32] buf %p\n", (void *)buf);
@@ -171,7 +184,7 @@
  * return the number of bytes packed, otherwise return -1 */
 gint qq_putdata(guint8 *buf, const guint8 *data, const int datalen)
 {
-    memcpy(buf, data, datalen);
+   	memcpy(buf, data, datalen);
 #ifdef PARSER_DEBUG
 	purple_debug_info("QQ", "[DBG][putdata] buf %p\n", (void *)buf);
 #endif
--- a/libpurple/protocols/qq/packet_parse.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/packet_parse.h	Thu Nov 06 03:20:05 2008 +0000
@@ -54,6 +54,7 @@
 gint qq_put16(guint8 *buf, guint16 w);
 gint qq_put32(guint8 *buf, guint32 dw);
 gint qq_putIP(guint8* buf, struct in_addr *ip);
+gint qq_putime(guint8 *buf, time_t *t);
 gint qq_putdata(guint8 *buf, const guint8 *data, const int datalen);
 
 /*
--- a/libpurple/protocols/qq/qq.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/qq.c	Thu Nov 06 03:20:05 2008 +0000
@@ -29,6 +29,7 @@
 #include "notify.h"
 #include "prefs.h"
 #include "prpl.h"
+#include "privacy.h"
 #include "request.h"
 #include "roomlist.h"
 #include "server.h"
@@ -39,12 +40,12 @@
 #include "buddy_list.h"
 #include "char_conv.h"
 #include "group.h"
-#include "group_find.h"
 #include "group_im.h"
 #include "group_info.h"
 #include "group_join.h"
 #include "group_opt.h"
-#include "header_info.h"
+#include "group_internal.h"
+#include "qq_define.h"
 #include "im.h"
 #include "qq_process.h"
 #include "qq_base.h"
@@ -56,7 +57,11 @@
 #include "version.h"
 
 #define OPENQ_AUTHOR            "Puzzlebird"
-#define OPENQ_WEBSITE            "http://openq.sourceforge.net"
+#define OPENQ_WEBSITE           "http://openq.sourceforge.net"
+
+#ifndef OPENQ_VERSION
+#define OPENQ_VERSION           DISPLAY_VERSION
+#endif
 
 static GList *server_list_build(gchar select)
 {
@@ -128,6 +133,7 @@
 	PurpleConnection *gc;
 	qq_data *qd;
 	PurplePresence *presence;
+	const gchar *version_str;
 
 	g_return_if_fail(account != NULL);
 
@@ -154,6 +160,19 @@
 	server_list_create(account);
 	purple_debug_info("QQ", "Server list has %d\n", g_list_length(qd->servers));
 
+	version_str = purple_account_get_string(account, "client_version", NULL);
+	qd->client_tag = QQ_CLIENT_0D55;	/* set default as QQ2005 */
+	qd->client_version = 2005;
+	if (version_str != NULL && strlen(version_str) != 0) {
+		if (strcmp(version_str, "qq2007") == 0) {
+			qd->client_tag = QQ_CLIENT_111D;
+			qd->client_version = 2007;
+		} else if (strcmp(version_str, "qq2008") == 0) {
+			qd->client_tag = QQ_CLIENT_115B;
+			qd->client_version = 2008;
+		}
+	}
+
 	qd->is_show_notice = purple_account_get_bool(account, "show_notice", TRUE);
 	qd->is_show_news = purple_account_get_bool(account, "show_news", TRUE);
 
@@ -203,6 +222,13 @@
 	}
 
 	qq_disconnect(gc);
+
+	if (qd->redirect) g_free(qd->redirect);
+	if (qd->ld.token) g_free(qd->ld.token);
+	if (qd->ld.token_ex) g_free(qd->ld.token_ex);
+	if (qd->captcha.token) g_free(qd->captcha.token);
+	if (qd->captcha.data) g_free(qd->captcha.data);
+
 	server_list_remove_all(qd);
 
 	g_free(qd);
@@ -210,25 +236,25 @@
 }
 
 /* returns the icon name for a buddy or protocol */
-static const gchar *_qq_list_icon(PurpleAccount *a, PurpleBuddy *b)
+static const gchar *qq_list_icon(PurpleAccount *a, PurpleBuddy *b)
 {
 	return "qq";
 }
 
 
 /* a short status text beside buddy icon*/
-static gchar *_qq_status_text(PurpleBuddy *b)
+static gchar *qq_status_text(PurpleBuddy *b)
 {
-	qq_buddy *q_bud;
+	qq_buddy_data *bd;
 	GString *status;
 
-	q_bud = (qq_buddy *) b->proto_data;
-	if (q_bud == NULL)
+	bd = (qq_buddy_data *) b->proto_data;
+	if (bd == NULL)
 		return NULL;
 
 	status = g_string_new("");
 
-	switch(q_bud->status) {
+	switch(bd->status) {
 	case QQ_BUDDY_OFFLINE:
 		g_string_append(status, _("Offline"));
 		break;
@@ -245,8 +271,11 @@
 	case QQ_BUDDY_ONLINE_INVISIBLE:
 		g_string_append(status, _("Invisible"));
 		break;
+	case QQ_BUDDY_ONLINE_BUSY:
+		g_string_append(status, _("Busy"));
+		break;
 	default:
-		g_string_printf(status, _("Unknown-%d"), q_bud->status);
+		g_string_printf(status, _("Unknown-%d"), bd->status);
 	}
 
 	return g_string_free(status, FALSE);
@@ -254,23 +283,23 @@
 
 
 /* a floating text when mouse is on the icon, show connection status here */
-static void _qq_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full)
+static void qq_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full)
 {
-	qq_buddy *q_bud;
+	qq_buddy_data *bd;
 	gchar *tmp;
 	GString *str;
 
 	g_return_if_fail(b != NULL);
 
-	q_bud = (qq_buddy *) b->proto_data;
-	if (q_bud == NULL)
+	bd = (qq_buddy_data *) b->proto_data;
+	if (bd == NULL)
 		return;
 
-	/* if (PURPLE_BUDDY_IS_ONLINE(b) && q_bud != NULL) */
-	if (q_bud->ip.s_addr != 0) {
+	/* if (PURPLE_BUDDY_IS_ONLINE(b) && bd != NULL) */
+	if (bd->ip.s_addr != 0) {
 		str = g_string_new(NULL);
-		g_string_printf(str, "%s:%d", inet_ntoa(q_bud->ip), q_bud->port);
-		if (q_bud->comm_flag & QQ_COMM_FLAG_TCP_MODE) {
+		g_string_printf(str, "%s:%d", inet_ntoa(bd->ip), bd->port);
+		if (bd->comm_flag & QQ_COMM_FLAG_TCP_MODE) {
 			g_string_append(str, " TCP");
 		} else {
 			g_string_append(str, " UDP");
@@ -278,11 +307,11 @@
 		g_string_free(str, TRUE);
 	}
 
-	tmp = g_strdup_printf("%d", q_bud->age);
+	tmp = g_strdup_printf("%d", bd->age);
 	purple_notify_user_info_add_pair(user_info, _("Age"), tmp);
 	g_free(tmp);
 
-	switch (q_bud->gender) {
+	switch (bd->gender) {
 	case QQ_BUDDY_GENDER_GG:
 		purple_notify_user_info_add_pair(user_info, _("Gender"), _("Male"));
 		break;
@@ -293,38 +322,38 @@
 		purple_notify_user_info_add_pair(user_info, _("Gender"), _("Unknown"));
 		break;
 	default:
-		tmp = g_strdup_printf("Error (%d)", q_bud->gender);
+		tmp = g_strdup_printf("Error (%d)", bd->gender);
 		purple_notify_user_info_add_pair(user_info, _("Gender"), tmp);
 		g_free(tmp);
 	}
 
-	if (q_bud->level) {
-		tmp = g_strdup_printf("%d", q_bud->level);
+	if (bd->level) {
+		tmp = g_strdup_printf("%d", bd->level);
 		purple_notify_user_info_add_pair(user_info, _("Level"), tmp);
 		g_free(tmp);
 	}
 
 	str = g_string_new(NULL);
-	if (q_bud->comm_flag & QQ_COMM_FLAG_QQ_MEMBER) {
+	if (bd->comm_flag & QQ_COMM_FLAG_QQ_MEMBER) {
 		g_string_append( str, _("Member") );
 	}
-	if (q_bud->comm_flag & QQ_COMM_FLAG_QQ_VIP) {
+	if (bd->comm_flag & QQ_COMM_FLAG_QQ_VIP) {
 		g_string_append( str, _(" VIP") );
 	}
-	if (q_bud->comm_flag & QQ_COMM_FLAG_TCP_MODE) {
+	if (bd->comm_flag & QQ_COMM_FLAG_TCP_MODE) {
 		g_string_append( str, _(" TCP") );
 	}
-	if (q_bud->comm_flag & QQ_COMM_FLAG_MOBILE) {
+	if (bd->comm_flag & QQ_COMM_FLAG_MOBILE) {
 		g_string_append( str, _(" FromMobile") );
 	}
-	if (q_bud->comm_flag & QQ_COMM_FLAG_BIND_MOBILE) {
+	if (bd->comm_flag & QQ_COMM_FLAG_BIND_MOBILE) {
 		g_string_append( str, _(" BindMobile") );
 	}
-	if (q_bud->comm_flag & QQ_COMM_FLAG_VIDEO) {
+	if (bd->comm_flag & QQ_COMM_FLAG_VIDEO) {
 		g_string_append( str, _(" Video") );
 	}
 
-	if (q_bud->ext_flag & QQ_EXT_FLAG_ZONE) {
+	if (bd->ext_flag & QQ_EXT_FLAG_ZONE) {
 		g_string_append( str, _(" Zone") );
 	}
 	purple_notify_user_info_add_pair(user_info, _("Flag"), str->str);
@@ -333,40 +362,47 @@
 
 #ifdef DEBUG
 	tmp = g_strdup_printf( "%s (%04X)",
-										qq_get_ver_desc(q_bud->client_version),
-										q_bud->client_version );
+										qq_get_ver_desc(bd->client_tag),
+										bd->client_tag );
 	purple_notify_user_info_add_pair(user_info, _("Ver"), tmp);
 	g_free(tmp);
 
 	tmp = g_strdup_printf( "Ext 0x%X, Comm 0x%X",
-												q_bud->ext_flag, q_bud->comm_flag );
+												bd->ext_flag, bd->comm_flag );
 	purple_notify_user_info_add_pair(user_info, _("Flag"), tmp);
 	g_free(tmp);
 #endif
 }
 
 /* we can show tiny icons on the four corners of buddy icon, */
-static const char *_qq_list_emblem(PurpleBuddy *b)
+static const char *qq_list_emblem(PurpleBuddy *b)
 {
-	/* each char** are refering to a filename in pixmaps/purple/status/default/ */
-	qq_buddy *q_bud;
+	PurpleAccount *account;
+	PurpleConnection *gc;
+	qq_data *qd;
+	qq_buddy_data *buddy;
 
-	if (!b || !(q_bud = b->proto_data)) {
+	if (!b || !(account = b->account) ||
+		!(gc = purple_account_get_connection(account)) || !(qd = gc->proto_data))
 		return NULL;
+
+	buddy = (qq_buddy_data *)b->proto_data;
+	if (!buddy) {
+		return "not-authorized";
 	}
 
-	if (q_bud->comm_flag & QQ_COMM_FLAG_MOBILE)
+	if (buddy->comm_flag & QQ_COMM_FLAG_MOBILE)
 		return "mobile";
-	if (q_bud->comm_flag & QQ_COMM_FLAG_VIDEO)
+	if (buddy->comm_flag & QQ_COMM_FLAG_VIDEO)
 		return "video";
-	if (q_bud->comm_flag & QQ_COMM_FLAG_QQ_MEMBER)
+	if (buddy->comm_flag & QQ_COMM_FLAG_QQ_MEMBER)
 		return "qq_member";
 
 	return NULL;
 }
 
 /* QQ away status (used to initiate QQ away packet) */
-static GList *_qq_away_states(PurpleAccount *ga)
+static GList *qq_status_types(PurpleAccount *ga)
 {
 	PurpleStatusType *status;
 	GList *types = NULL;
@@ -383,6 +419,10 @@
 			"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_OFFLINE,
 			"offline", _("Offline"), FALSE, TRUE, FALSE);
 	types = g_list_append(types, status);
@@ -395,18 +435,72 @@
 }
 
 /* initiate QQ away with proper change_status packet */
-static void _qq_change_status(PurpleAccount *account, PurpleStatus *status)
+static void qq_change_status(PurpleAccount *account, PurpleStatus *status)
 {
 	PurpleConnection *gc = purple_account_get_connection(account);
 
 	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)
+static gint qq_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, PurpleMessageFlags flags)
 {
-	gint type, to_uid;
+	gint type, uid_to;
 	gchar *msg, *msg_with_qq_smiley;
 	qq_data *qd;
 
@@ -417,15 +511,15 @@
 	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);
-	to_uid = purple_name_to_uid(who);
+	uid_to = purple_name_to_uid(who);
 
 	/* if msg is to myself, bypass the network */
-	if (to_uid == qd->uid) {
+	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_send_packet_im(gc, to_uid, msg_with_qq_smiley, type);
+		qq_request_send_im(gc, uid_to, msg_with_qq_smiley, type);
 		g_free(msg);
 		g_free(msg_with_qq_smiley);
 	}
@@ -434,21 +528,18 @@
 }
 
 /* send a chat msg to a QQ Qun */
-static int _qq_chat_send(PurpleConnection *gc, int channel, const char *message, PurpleMessageFlags flags)
+static int qq_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags)
 {
 	gchar *msg, *msg_with_qq_smiley;
-	qq_group *group;
+	guint32 room_id = id;
 
 	g_return_val_if_fail(message != NULL, -1);
 	g_return_val_if_fail(strlen(message) <= QQ_MSG_IM_MAX, -E2BIG);
 
-	group = qq_group_find_by_channel(gc, channel);
-	g_return_val_if_fail(group != NULL, -1);
-
 	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_send_packet_group_im(gc, group, msg_with_qq_smiley);
+	qq_request_room_send_im(gc, room_id, msg_with_qq_smiley);
 	g_free(msg);
 	g_free(msg_with_qq_smiley);
 
@@ -456,7 +547,7 @@
 }
 
 /* send packet to get who's detailed information */
-static void _qq_get_info(PurpleConnection *gc, const gchar *who)
+static void qq_show_buddy_info(PurpleConnection *gc, const gchar *who)
 {
 	guint32 uid;
 	qq_data *qd;
@@ -470,78 +561,135 @@
 		return;
 	}
 
-	qq_request_get_level(gc, uid);
-	qq_send_packet_get_info(gc, uid, TRUE);
+	if (qd->client_version >= 2007) {
+		qq_request_get_level_2007(gc, uid);
+	} else {
+		qq_request_get_level(gc, uid);
+	}
+	qq_request_buddy_info(gc, uid, 0, QQ_BUDDY_INFO_DISPLAY);
+}
+
+static void action_update_all_rooms(PurplePluginAction *action)
+{
+	PurpleConnection *gc = (PurpleConnection *) action->context;
+	qq_data *qd;
+
+	g_return_if_fail(NULL != gc && NULL != gc->proto_data);
+	qd = (qq_data *) gc->proto_data;
+
+	if ( !qd->is_login ) {
+		return;
+	}
+
+	qq_update_all_rooms(gc, 0, 0);
 }
 
-/* get my own information */
-static void _qq_menu_modify_my_info(PurplePluginAction *action)
+static void action_change_icon(PurplePluginAction *action)
+{
+	PurpleConnection *gc = (PurpleConnection *) action->context;
+	qq_data *qd;
+	gchar *icon_name;
+	gchar *icon_path;
+
+	g_return_if_fail(NULL != gc && NULL != gc->proto_data);
+	qd = (qq_data *) gc->proto_data;
+
+	if ( !qd->is_login ) {
+		return;
+	}
+
+	icon_name = qq_get_icon_name(qd->my_icon);
+	icon_path = qq_get_icon_path(icon_name);
+	g_free(icon_name);
+
+	purple_debug_info("QQ", "Change prev icon %s to ...\n", icon_path);
+	purple_request_file(action, _("Select icon..."), icon_path,
+			FALSE,
+			G_CALLBACK(qq_change_icon_cb), NULL,
+			purple_connection_get_account(gc), NULL, NULL,
+			gc);
+	g_free(icon_path);
+}
+
+static void action_modify_info_base(PurplePluginAction *action)
+{
+	PurpleConnection *gc = (PurpleConnection *) action->context;
+	qq_data *qd;
+
+	g_return_if_fail(NULL != gc && NULL != gc->proto_data);
+	qd = (qq_data *) gc->proto_data;
+	qq_request_buddy_info(gc, qd->uid, 0, QQ_BUDDY_INFO_MODIFY_BASE);
+}
+
+static void action_modify_info_ext(PurplePluginAction *action)
 {
 	PurpleConnection *gc = (PurpleConnection *) action->context;
 	qq_data *qd;
 
+	g_return_if_fail(NULL != gc && NULL != gc->proto_data);
 	qd = (qq_data *) gc->proto_data;
-	qq_prepare_modify_info(gc);
+	qq_request_buddy_info(gc, qd->uid, 0, QQ_BUDDY_INFO_MODIFY_EXT);
+}
+
+static void action_modify_info_addr(PurplePluginAction *action)
+{
+	PurpleConnection *gc = (PurpleConnection *) action->context;
+	qq_data *qd;
+
+	g_return_if_fail(NULL != gc && NULL != gc->proto_data);
+	qd = (qq_data *) gc->proto_data;
+	qq_request_buddy_info(gc, qd->uid, 0, QQ_BUDDY_INFO_MODIFY_ADDR);
 }
 
-static void _qq_menu_change_password(PurplePluginAction *action)
+static void action_modify_info_contact(PurplePluginAction *action)
 {
+	PurpleConnection *gc = (PurpleConnection *) action->context;
+	qq_data *qd;
+
+	g_return_if_fail(NULL != gc && NULL != gc->proto_data);
+	qd = (qq_data *) gc->proto_data;
+	qq_request_buddy_info(gc, qd->uid, 0, QQ_BUDDY_INFO_MODIFY_CONTACT);
+}
+
+static void action_change_password(PurplePluginAction *action)
+{
+	PurpleConnection *gc = (PurpleConnection *) action->context;
+
+	g_return_if_fail(NULL != gc && NULL != gc->proto_data);
 	purple_notify_uri(NULL, "https://password.qq.com");
 }
 
-/* remove a buddy from my list and remove myself from his list */
-/* TODO: re-enable this
-static void _qq_menu_block_buddy(PurpleBlistNode * node)
-{
-	guint32 uid;
-	gc_and_uid *g;
-	PurpleBuddy *buddy;
-	PurpleConnection *gc;
-	const gchar *who;
-
-	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
-
-	buddy = (PurpleBuddy *) node;
-	gc = purple_account_get_connection(buddy->account);
-	who = buddy->name;
-	g_return_if_fail(who != NULL);
-
-	uid = purple_name_to_uid(who);
-	g_return_if_fail(uid > 0);
-
-	g = g_new0(gc_and_uid, 1);
-	g->gc = gc;
-	g->uid = uid;
-
-	purple_request_action(gc, _("Block Buddy"),
-			    _("Are you sure you want to block this buddy?"), NULL,
-			    1, g, 2,
-			    _("Cancel"),
-			    G_CALLBACK(qq_do_nothing_with_gc_and_uid),
-			    _("Block"), G_CALLBACK(qq_block_buddy_with_gc_and_uid));
-}
-*/
-
 /* show a brief summary of what we get from login packet */
-static void _qq_menu_account_info(PurplePluginAction *action)
+static void action_show_account_info(PurplePluginAction *action)
 {
 	PurpleConnection *gc = (PurpleConnection *) action->context;
 	qq_data *qd;
 	GString *info;
+	struct tm *tm_local;
+	int index;
 
+	g_return_if_fail(NULL != gc && NULL != gc->proto_data);
 	qd = (qq_data *) gc->proto_data;
-	info = g_string_new("<html><body>\n");
+	info = g_string_new("<html><body>");
 
-	g_string_append_printf(info, _("<b>Current Online</b>: %d<br>\n"), qd->total_online);
-	g_string_append_printf(info, _("<b>Last Refresh</b>: %s<br>\n"), ctime(&qd->last_get_online));
+	tm_local = localtime(&qd->login_time);
+	g_string_append_printf(info, _("<b>Login time</b>: %d-%d-%d, %d:%d:%d<br>\n"),
+			(1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday,
+			tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec);
+	g_string_append_printf(info, _("<b>Total Online Buddies</b>: %d<br>\n"), qd->online_total);
+	tm_local = localtime(&qd->online_last_update);
+	g_string_append_printf(info, _("<b>Last Refresh</b>: %d-%d-%d, %d:%d:%d<br>\n"),
+			(1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday,
+			tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec);
 
-	g_string_append(info, "<hr>\n");
+	g_string_append(info, "<hr>");
 
 	g_string_append_printf(info, _("<b>Server</b>: %s<br>\n"), qd->curr_server);
+	g_string_append_printf(info, _("<b>Client Tag</b>: %s<br>\n"), qq_get_ver_desc(qd->client_tag));
 	g_string_append_printf(info, _("<b>Connection Mode</b>: %s<br>\n"), qd->use_tcp ? "TCP" : "UDP");
-	g_string_append_printf(info, _("<b>My Internet Address</b>: %s<br>\n"), inet_ntoa(qd->my_ip));
+	g_string_append_printf(info, _("<b>My Internet IP</b>: %s:%d<br>\n"), inet_ntoa(qd->my_ip), qd->my_port);
 
-	g_string_append(info, "<hr>\n");
+	g_string_append(info, "<hr>");
 	g_string_append(info, "<i>Network Status</i><br>\n");
 	g_string_append_printf(info, _("<b>Sent</b>: %lu<br>\n"), qd->net_stat.sent);
 	g_string_append_printf(info, _("<b>Resend</b>: %lu<br>\n"), qd->net_stat.resend);
@@ -549,12 +697,18 @@
 	g_string_append_printf(info, _("<b>Received</b>: %lu<br>\n"), qd->net_stat.rcved);
 	g_string_append_printf(info, _("<b>Received Duplicate</b>: %lu<br>\n"), qd->net_stat.rcved_dup);
 
-	g_string_append(info, "<hr>\n");
-	g_string_append(info, "<i>Information below may not be accurate</i><br>\n");
+	g_string_append(info, "<hr>");
+	g_string_append(info, "<i>Last Login Information</i><br>\n");
 
-	g_string_append_printf(info, _("<b>Login Time</b>: %s<br>\n"), ctime(&qd->login_time));
-	g_string_append_printf(info, _("<b>Last Login IP</b>: %s<br>\n"), qd->last_login_ip);
-	g_string_append_printf(info, _("<b>Last Login Time</b>: %s\n"), ctime(&qd->last_login_time));
+	for (index = 0; index < sizeof(qd->last_login_time) / sizeof(time_t); index++) {
+		tm_local = localtime(&qd->last_login_time[index]);
+		g_string_append_printf(info, _("<b>Time</b>: %d-%d-%d, %d:%d:%d<br>\n"),
+				(1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday,
+				tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec);
+	}
+	if (qd->last_login_ip.s_addr != 0) {
+		g_string_append_printf(info, _("<b>IP</b>: %s<br>\n"), inet_ntoa(qd->last_login_ip));
+	}
 
 	g_string_append(info, "</body></html>");
 
@@ -563,6 +717,65 @@
 	g_string_free(info, TRUE);
 }
 
+static void action_about_openq(PurplePluginAction *action)
+{
+	PurpleConnection *gc = (PurpleConnection *) action->context;
+	qq_data *qd;
+	GString *info;
+	gchar *title;
+
+	g_return_if_fail(NULL != gc && NULL != gc->proto_data);
+	qd = (qq_data *) gc->proto_data;
+
+	info = g_string_new("<html><body>");
+	g_string_append(info, _("<p><b>Original Author</b>:<br>\n"));
+	g_string_append(info, "puzzlebird<br>\n");
+	g_string_append(info, "<br>\n");
+	g_string_append(info, _("<p><b>Code Contributors</b>:<br>\n"));
+	g_string_append(info, "gfhuang : patches for libpurple 2.0.0beta2, maintainer<br>\n");
+	g_string_append(info, "Yuan Qingyun : patches for libpurple 1.5.0, maintainer<br>\n");
+	g_string_append(info, "henryouly : file transfer, udp sock5 proxy and qq_show, maintainer<br>\n");
+	g_string_append(info, "hzhr : maintainer<br>\n");
+	g_string_append(info, "joymarquis : maintainer<br>\n");
+	g_string_append(info, "arfankai : fixed bugs in char_conv.c<br>\n");
+	g_string_append(info, "rakescar : provided filter for HTML tag<br>\n");
+	g_string_append(info, "yyw : improved performance on PPC linux<br>\n");
+	g_string_append(info, "lvxiang : provided ip to location original code<br>\n");
+	g_string_append(info, "markhuetsch : OpenQ merge into libpurple, maintainer 2006-2007<br>\n");
+	g_string_append(info, "ccpaging : maintainer since 2007<br>\n");
+	g_string_append(info, "icesky : maintainer since 2007<br>\n");
+	g_string_append(info, "csyfek : faces, maintainer since 2007<br>\n");
+	g_string_append(info, "<br>\n");
+	g_string_append(info, _("<p><b>Lovely Patch Writers</b>:<br>\n"));
+	g_string_append(info, "gnap : message displaying, documentation<br>\n");
+	g_string_append(info, "manphiz : qun processing<br>\n");
+	g_string_append(info, "moo : qun processing<br>\n");
+	g_string_append(info, "Coly Li : qun processing<br>\n");
+	g_string_append(info, "Emil Alexiev : captcha verification on login based on LumaQQ for MAC (2007), login, add buddy, remove buddy, message exchange and logout<br>\n");
+	g_string_append(info, "<br>\n");
+	g_string_append(info, _("<p><b>Acknowledgement</b>:<br>\n"));
+	g_string_append(info, "Shufeng Tan : http://sf.net/projects/perl-oicq<br>\n");
+	g_string_append(info, "Jeff Ye : http://www.sinomac.com<br>\n");
+	g_string_append(info, "Hu Zheng : http://forlinux.yeah.net<br>\n");
+	g_string_append(info, "yunfan : http://www.myswear.net<br>\n");
+	g_string_append(info, "OpenQ Team : http://openq.linuxsir.org<br>\n");
+	g_string_append(info, "LumaQQ Team : http://lumaqq.linuxsir.org<br>\n");
+	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, "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);
+	purple_notify_formatted(gc, NULL, title, NULL, info->str, NULL, NULL);
+
+	g_free(title);
+	g_string_free(info, TRUE);
+}
+
 /*
 static void _qq_menu_search_or_add_permanent_group(PurplePluginAction *action)
 {
@@ -578,35 +791,48 @@
 			   _("Input Qun name here"),
 			   _("Only QQ members can create permanent Qun"),
 			   "OpenQ", FALSE, FALSE, NULL,
-			   _("Create"), G_CALLBACK(qq_room_create_new), _("Cancel"), NULL, gc);
+			   _("Create"), G_CALLBACK(qq_create_room), _("Cancel"), NULL, gc);
 }
 */
 
-static void _qq_menu_unsubscribe_group(PurpleBlistNode * node)
+static void action_chat_quit(PurpleBlistNode * node)
 {
 	PurpleChat *chat = (PurpleChat *)node;
 	PurpleConnection *gc = purple_account_get_connection(chat->account);
 	GHashTable *components = chat -> components;
+	gchar *num_str;
+	guint32 room_id;
 
 	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
 
 	g_return_if_fail(components != NULL);
-	qq_group_exit(gc, components);
+
+	num_str = g_hash_table_lookup(components, QQ_ROOM_KEY_INTERNAL_ID);
+	room_id = strtol(num_str, NULL, 10);
+	g_return_if_fail(room_id != 0);
+
+	qq_room_quit(gc, room_id);
 }
 
-/*
-static void _qq_menu_manage_group(PurpleBlistNode * node)
+static void action_chat_get_info(PurpleBlistNode * node)
 {
 	PurpleChat *chat = (PurpleChat *)node;
 	PurpleConnection *gc = purple_account_get_connection(chat->account);
 	GHashTable *components = chat -> components;
+	gchar *num_str;
+	guint32 room_id;
 
 	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
 
 	g_return_if_fail(components != NULL);
-	qq_group_manage_group(gc, components);
+
+	num_str = g_hash_table_lookup(components, QQ_ROOM_KEY_INTERNAL_ID);
+	room_id = strtol(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,
+			0, QQ_ROOM_INFO_DISPLAY);
 }
-*/
 
 #if 0
 /* TODO: re-enable this */
@@ -614,12 +840,12 @@
 {
 	PurpleBuddy *buddy;
 	PurpleConnection *gc;
-	qq_buddy *q_bud;
+	qq_buddy_data *bd;
 
 	g_return_if_fail (PURPLE_BLIST_NODE_IS_BUDDY (node));
 	buddy = (PurpleBuddy *) node;
-	q_bud = (qq_buddy *) buddy->proto_data;
-/*	if (is_online (q_bud->status)) { */
+	bd = (qq_buddy_data *) buddy->proto_data;
+/*	if (is_online (bd->status)) { */
 	gc = purple_account_get_connection (buddy->account);
 	g_return_if_fail (gc != NULL && gc->proto_data != NULL);
 	qq_send_file(gc, buddy->name, NULL);
@@ -628,21 +854,38 @@
 #endif
 
 /* protocol related menus */
-static GList *_qq_actions(PurplePlugin *plugin, gpointer context)
+static GList *qq_actions(PurplePlugin *plugin, gpointer context)
 {
 	GList *m;
 	PurplePluginAction *act;
 
 	m = NULL;
-	act = purple_plugin_action_new(_("Set My Information"), _qq_menu_modify_my_info);
+	act = purple_plugin_action_new(_("Change Icon"), action_change_icon);
+	m = g_list_append(m, act);
+
+	act = purple_plugin_action_new(_("Modify Information"), action_modify_info_base);
+	m = g_list_append(m, act);
+
+	act = purple_plugin_action_new(_("Modify Extend Information"), action_modify_info_ext);
+	m = g_list_append(m, act);
+
+	act = purple_plugin_action_new(_("Modify Address"), action_modify_info_addr);
 	m = g_list_append(m, act);
 
-	act = purple_plugin_action_new(_("Change Password"), _qq_menu_change_password);
+	act = purple_plugin_action_new(_("Modify Contact"), action_modify_info_contact);
+	m = g_list_append(m, act);
+
+	act = purple_plugin_action_new(_("Change Password"), action_change_password);
 	m = g_list_append(m, act);
 
-	act = purple_plugin_action_new(_("Account Information"), _qq_menu_account_info);
+	act = purple_plugin_action_new(_("Account Information"), action_show_account_info);
 	m = g_list_append(m, act);
 
+	act = purple_plugin_action_new(_("Update all QQ Quns"), action_update_all_rooms);
+	m = g_list_append(m, act);
+
+	act = purple_plugin_action_new(_("About OpenQ"), action_about_openq);
+	m = g_list_append(m, act);
 	/*
 	act = purple_plugin_action_new(_("Qun: Search a permanent Qun"), _qq_menu_search_or_add_permanent_group);
 	m = g_list_append(m, act);
@@ -654,63 +897,131 @@
 	return m;
 }
 
+static void qq_add_buddy_from_menu_cb(PurpleBlistNode *node, gpointer data)
+{
+	PurpleBuddy *buddy;
+	PurpleConnection *gc;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
+
+	buddy = (PurpleBuddy *) node;
+	gc = purple_account_get_connection(buddy->account);
+
+	qq_add_buddy(gc, buddy, NULL);
+}
+
+static GList *qq_buddy_menu(PurpleBuddy *buddy)
+{
+	GList *m = NULL;
+	PurpleMenuAction *act;
+	/*
+	PurpleConnection *gc = purple_account_get_connection(buddy->account);
+	qq_data *qd = gc->proto_data;
+	*/
+	qq_buddy_data *bd = (qq_buddy_data *)buddy->proto_data;
+
+	if (bd == NULL) {
+		act = purple_menu_action_new(_("Add Buddy"),
+		                           PURPLE_CALLBACK(qq_add_buddy_from_menu_cb),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+
+		return m;
+
+	}
+
+/* TODO : not working, temp commented out by gfhuang */
+#if 0
+	if (bd && is_online(bd->status)) {
+		act = purple_menu_action_new(_("Send File"), PURPLE_CALLBACK(_qq_menu_send_file), NULL, NULL); /* add NULL by gfhuang */
+		m = g_list_append(m, act);
+	}
+#endif
+	return m;
+}
+
 /* chat-related (QQ Qun) menu shown up with right-click */
-static GList *_qq_chat_menu(PurpleBlistNode *node)
+static GList *qq_chat_menu(PurpleBlistNode *node)
 {
 	GList *m;
 	PurpleMenuAction *act;
 
 	m = NULL;
-	act = purple_menu_action_new(_("Leave the QQ Qun"), PURPLE_CALLBACK(_qq_menu_unsubscribe_group), NULL, NULL);
+	act = purple_menu_action_new(_("Get Info"), PURPLE_CALLBACK(action_chat_get_info), NULL, NULL);
 	m = g_list_append(m, act);
 
-	/* TODO: enable this
-	act = purple_menu_action_new(_("Show Details"), PURPLE_CALLBACK(_qq_menu_manage_group), NULL, NULL);
+	act = purple_menu_action_new(_("Quit Qun"), PURPLE_CALLBACK(action_chat_quit), NULL, NULL);
 	m = g_list_append(m, act);
-	*/
-
 	return m;
 }
 
 /* buddy-related menu shown up with right-click */
-static GList *_qq_buddy_menu(PurpleBlistNode * node)
+static GList *qq_blist_node_menu(PurpleBlistNode * node)
 {
-	GList *m;
+	if(PURPLE_BLIST_NODE_IS_CHAT(node))
+		return qq_chat_menu(node);
 
-	if(PURPLE_BLIST_NODE_IS_CHAT(node))
-		return _qq_chat_menu(node);
+	if(PURPLE_BLIST_NODE_IS_BUDDY(node))
+		return qq_buddy_menu((PurpleBuddy *) node);
 
-	m = NULL;
+	return NULL;
+}
 
-/* TODO : not working, temp commented out by gfhuang */
-#if 0
+/* convert name displayed in a chat channel to original QQ UID */
+static gchar *chat_name_to_purple_name(const gchar *const name)
+{
+	const char *start;
+	const char *end;
+	gchar *ret;
+
+	g_return_val_if_fail(name != NULL, NULL);
 
-	act = purple_menu_action_new(_("Block this buddy"), PURPLE_CALLBACK(_qq_menu_block_buddy), NULL, NULL); /* add NULL by gfhuang */
-	m = g_list_append(m, act);
-/*	if (q_bud && is_online(q_bud->status)) { */
-		act = purple_menu_action_new(_("Send File"), PURPLE_CALLBACK(_qq_menu_send_file), NULL, NULL); /* add NULL by gfhuang */
-		m = g_list_append(m, act);
-/*	} */
-#endif
+	/* Sample: (1234567)*/
+	start = strchr(name, '(');
+	g_return_val_if_fail(start != NULL, NULL);
+	end = strchr(start, ')');
+	g_return_val_if_fail(end != NULL && (end - start) > 1, NULL);
 
-	return m;
+	ret = g_strndup(start + 1, end - start - 1);
+
+	return ret;
 }
 
-/* convert chat nickname to qq-uid to get this buddy info */
+/* convert chat nickname to uid to get this buddy info */
 /* who is the nickname of buddy in QQ chat-room (Qun) */
-static void _qq_get_chat_buddy_info(PurpleConnection *gc, gint channel, const gchar *who)
+static void qq_get_chat_buddy_info(PurpleConnection *gc, gint channel, const gchar *who)
 {
-	gchar *purple_name;
+	qq_data *qd;
+	gchar *uid_str;
+	guint32 uid;
+
+	purple_debug_info("QQ", "Get chat buddy info of %s\n", who);
 	g_return_if_fail(who != NULL);
 
-	purple_name = chat_name_to_purple_name(who);
-	if (purple_name != NULL)
-		_qq_get_info(gc, purple_name);
+	uid_str = chat_name_to_purple_name(who);
+	if (uid_str == NULL) {
+		return;
+	}
+
+	qd = gc->proto_data;
+	uid = purple_name_to_uid(uid_str);
+	g_free(uid_str);
+
+	if (uid <= 0) {
+		purple_debug_error("QQ", "Not valid chat name: %s\n", who);
+		purple_notify_error(gc, NULL, _("Invalid name"), NULL);
+		return;
+	}
+
+	if (qd->client_version < 2007) {
+		qq_request_get_level(gc, uid);
+	}
+	qq_request_buddy_info(gc, uid, 0, QQ_BUDDY_INFO_DISPLAY);
 }
 
-/* convert chat nickname to qq-uid to invite individual IM to buddy */
+/* convert chat nickname to uid to invite individual IM to buddy */
 /* who is the nickname of buddy in QQ chat-room (Qun) */
-static gchar *_qq_get_chat_buddy_real_name(PurpleConnection *gc, gint channel, const gchar *who)
+static gchar *qq_get_chat_buddy_real_name(PurpleConnection *gc, gint channel, const gchar *who)
 {
 	g_return_val_if_fail(who != NULL, NULL);
 	return chat_name_to_purple_name(who);
@@ -722,21 +1033,21 @@
 	NULL,							/* user_splits	*/
 	NULL,							/* protocol_options */
 	{"png", 96, 96, 96, 96, 0, PURPLE_ICON_SCALE_SEND}, /* icon_spec */
-	_qq_list_icon,						/* list_icon */
-	_qq_list_emblem,					/* list_emblems */
-	_qq_status_text,					/* status_text	*/
-	_qq_tooltip_text,					/* tooltip_text */
-	_qq_away_states,					/* away_states	*/
-	_qq_buddy_menu,						/* blist_node_menu */
+	qq_list_icon,						/* list_icon */
+	qq_list_emblem,					/* list_emblems */
+	qq_status_text,					/* status_text	*/
+	qq_tooltip_text,					/* tooltip_text */
+	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_send_im,						/* send_im */
 	NULL,							/* set_info */
 	NULL,							/* send_typing	*/
-	_qq_get_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 */
@@ -744,30 +1055,30 @@
 	qq_remove_buddy,					/* remove_buddy */
 	NULL,							/* remove_buddies */
 	NULL,							/* add_permit */
-	NULL,							/* add_deny */
+	qq_add_deny,							/* add_deny */
 	NULL,							/* rem_permit */
 	NULL,							/* rem_deny */
-	NULL,							/* set_permit_deny */
+	qq_set_permit_deny,			/* set_permit_deny */
 	qq_group_join,						/* join_chat */
 	NULL,							/* reject chat	invite */
 	NULL,							/* get_chat_name */
 	NULL,							/* chat_invite	*/
 	NULL,							/* chat_leave */
 	NULL,							/* chat_whisper */
-	_qq_chat_send,			/* chat_send */
+	qq_chat_send,			/* chat_send */
 	NULL,							/* keepalive */
 	NULL,							/* register_user */
-	_qq_get_chat_buddy_info,				/* get_cb_info	*/
+	qq_get_chat_buddy_info,				/* get_cb_info	*/
 	NULL,							/* get_cb_away	*/
 	NULL,							/* alias_buddy	*/
-	NULL,							/* group_buddy	*/
+	NULL,							/* change buddy's group	*/
 	NULL,							/* rename_group */
 	NULL,							/* buddy_free */
 	NULL,							/* convo_closed */
 	NULL,							/* normalize */
-	qq_set_my_buddy_icon,					/* set_buddy_icon */
+	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 */
@@ -817,7 +1128,7 @@
 	NULL,				/**< ui_info		*/
 	&prpl_info,			/**< extra_info		*/
 	NULL,				/**< prefs_info		*/
-	_qq_actions,
+	qq_actions,
 
 	/* padding */
 	NULL,
@@ -831,35 +1142,60 @@
 {
 	PurpleAccountOption *option;
 	PurpleKeyValuePair *kvp;
-	GList *list = NULL;
-	GList *kvlist = NULL;
-	GList *entry;
+	GList *server_list = NULL;
+	GList *server_kv_list = NULL;
+	GList *it;
+//#ifdef DEBUG
+	GList *version_kv_list = NULL;
+//#endif
 
-	list = server_list_build('A');
+	server_list = server_list_build('A');
 
-	purple_prefs_add_string_list("/plugins/prpl/qq/serverlist", list);
-	list = purple_prefs_get_string_list("/plugins/prpl/qq/serverlist");
+	purple_prefs_add_string_list("/plugins/prpl/qq/serverlist", server_list);
+	server_list = purple_prefs_get_string_list("/plugins/prpl/qq/serverlist");
 
-	kvlist = NULL;
+	server_kv_list = NULL;
 	kvp = g_new0(PurpleKeyValuePair, 1);
 	kvp->key = g_strdup(_("Auto"));
 	kvp->value = g_strdup("auto");
-	kvlist = g_list_append(kvlist, kvp);
+	server_kv_list = g_list_append(server_kv_list, kvp);
 
-	entry = list;
-	while(entry) {
-		if (entry->data != NULL && strlen(entry->data) > 0) {
+	it = server_list;
+	while(it) {
+		if (it->data != NULL && strlen(it->data) > 0) {
 			kvp = g_new0(PurpleKeyValuePair, 1);
-			kvp->key = g_strdup(entry->data);
-			kvp->value = g_strdup(entry->data);
-			kvlist = g_list_append(kvlist, kvp);
+			kvp->key = g_strdup(it->data);
+			kvp->value = g_strdup(it->data);
+			server_kv_list = g_list_append(server_kv_list, kvp);
 		}
-		entry = entry->next;
+		it = it->next;
 	}
 
-	option = purple_account_option_list_new(_("Server"), "server", kvlist);
+	g_list_free(server_list);
+
+	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");
+	version_kv_list = g_list_append(version_kv_list, kvp);
+
+	kvp = g_new0(PurpleKeyValuePair, 1);
+	kvp->key = g_strdup(_("QQ2007"));
+	kvp->value = g_strdup("qq2007");
+	version_kv_list = g_list_append(version_kv_list, kvp);
+
+	kvp = g_new0(PurpleKeyValuePair, 1);
+	kvp->key = g_strdup(_("QQ2008"));
+	kvp->value = g_strdup("qq2008");
+	version_kv_list = g_list_append(version_kv_list, kvp);
+
+	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);
 
@@ -878,9 +1214,10 @@
 	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/show_room_when_newin", TRUE);
+	purple_prefs_add_bool("/plugins/prpl/qq/auto_popup_conversation", FALSE);
+	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", 4);
+	purple_prefs_add_int("/plugins/prpl/qq/resend_times", 10);
 }
 
 PURPLE_INIT_PLUGIN(qq, init_plugin, info);
--- a/libpurple/protocols/qq/qq.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/qq.h	Thu Nov 06 03:20:05 2008 +0000
@@ -37,9 +37,34 @@
 #define QQ_KEY_LENGTH       16
 
 typedef struct _qq_data qq_data;
-typedef struct _qq_buddy qq_buddy;
+typedef struct _qq_buddy_data qq_buddy_data;
 typedef struct _qq_interval qq_interval;
 typedef struct _qq_net_stat qq_net_stat;
+typedef struct _qq_login_data qq_login_data;
+typedef struct _qq_captcha_data qq_captcha_data;
+
+struct _qq_captcha_data {
+	guint8 *token;
+	guint16 token_len;
+	guint8 next_index;
+	guint8 *data;
+	guint16 data_len;
+};
+
+struct _qq_login_data {
+	guint8 random_key[QQ_KEY_LENGTH];			/* first encrypt key generated by client */
+	guint8 *token;				/* get from server */
+	guint8 token_len;
+	guint8 *token_ex;			/* get from server */
+	guint16 token_ex_len;
+
+	guint8 pwd_md5[QQ_KEY_LENGTH];			/* password in md5 (or md5' md5) */
+	guint8 pwd_twice_md5[QQ_KEY_LENGTH];
+
+	guint8 *login_token;
+	guint16 login_token_len;
+	guint8 login_key[QQ_KEY_LENGTH];
+};
 
 struct _qq_interval {
 	gint resend;
@@ -55,7 +80,7 @@
 	glong rcved_dup;
 };
 
-struct _qq_buddy {
+struct _qq_buddy_data {
 	guint32 uid;
 	guint16 face;		/* index: 0 - 299 */
 	guint8 age;
@@ -66,7 +91,7 @@
 	guint8 status;
 	guint8 ext_flag;
 	guint8 comm_flag;	/* details in qq_buddy_list.c */
-	guint16 client_version;
+	guint16 client_tag;
 	guint8 onlineTime;
 	guint16 level;
 	guint16 timeRemainder;
@@ -105,8 +130,14 @@
 	GList *servers;
 	gchar *curr_server;		/* point to servers->data, do not free*/
 
+	guint16 client_tag;
+	gint client_version;
+
 	struct in_addr redirect_ip;
 	guint16 redirect_port;
+	guint8 *redirect;
+	guint8 redirect_len;
+
 	guint check_watcher;
 	guint connect_watcher;
 	gint connect_retry;
@@ -119,46 +150,35 @@
 	GList *transactions;	/* check ack packet and resend */
 
 	guint32 uid;			/* QQ number */
-	guint8 *token;		/* get from server*/
-	int token_len;
-	guint8 inikey[QQ_KEY_LENGTH];			/* initial key to encrypt login packet */
-	guint8 password_twice_md5[QQ_KEY_LENGTH];			/* password in md5 (or md5' md5) */
+
+	qq_login_data ld;
+	qq_captcha_data captcha;
+
 	guint8 session_key[QQ_KEY_LENGTH];		/* later use this as key in this session */
 	guint8 session_md5[QQ_KEY_LENGTH];		/* concatenate my uid with session_key and md5 it */
 
 	guint16 send_seq;		/* send sequence number */
 	guint8 login_mode;		/* online of invisible */
-	gboolean is_login;		/* used by qq-add_buddy */
+	gboolean is_login;		/* used by qq_add_buddy */
 
 	PurpleXfer *xfer;			/* file transfer handler */
 
 	/* get from login reply packet */
+	struct in_addr my_local_ip;			/* my local ip address detected by server */
+	guint16 my_local_port;		/* my lcoal port detected by server */
 	time_t login_time;
-	time_t last_login_time;
-	gchar *last_login_ip;
+	time_t last_login_time[3];
+	struct in_addr last_login_ip;
 	/* get from keep_alive packet */
 	struct in_addr my_ip;			/* my ip address detected by server */
 	guint16 my_port;		/* my port detected by server */
 	guint16 my_icon;		/* my icon index */
-	guint16 my_level;		/* my level */
-	guint32 total_online;		/* the number of online QQ users */
-	time_t last_get_online;		/* last time send get_friends_online packet */
+	guint32 online_total;		/* the number of online QQ users */
+	time_t online_last_update;		/* last time send get_friends_online packet */
 
 	PurpleRoomlist *roomlist;
-	gint channel;			/* the id for opened chat conversation */
 
 	GList *groups;
-	GSList *joining_groups;
-	GSList *adding_groups_from_server; /* internal ids of groups the server wants in my blist */
-	GList *buddies;
-	GList *contact_info_window;
-	GList *group_info_window;
-	GList *info_query;
-	GList *add_buddy_request;
-
-	/* TODO pass qq_send_packet_get_info() a callback and use signals to get rid of these */
-	gboolean modifying_info;
-	gboolean modifying_face;
 
 	gboolean is_show_notice;
 	gboolean is_show_news;
--- a/libpurple/protocols/qq/qq_base.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/qq_base.c	Thu Nov 06 03:20:05 2008 +0000
@@ -26,22 +26,335 @@
 #include "internal.h"
 #include "server.h"
 #include "cipher.h"
+#include "request.h"
 
 #include "buddy_info.h"
 #include "buddy_list.h"
 #include "char_conv.h"
 #include "qq_crypt.h"
 #include "group.h"
-#include "header_info.h"
+#include "qq_define.h"
+#include "qq_network.h"
 #include "qq_base.h"
 #include "packet_parse.h"
 #include "qq.h"
 #include "qq_network.h"
 #include "utils.h"
 
-#define QQ_LOGIN_DATA_LENGTH		    416
-#define QQ_LOGIN_REPLY_OK_PACKET_LEN        139
-#define QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN  11
+/* generate a md5 key using uid and session_key */
+static void get_session_md5(guint8 *session_md5, guint32 uid, guint8 *session_key)
+{
+	guint8 src[QQ_KEY_LENGTH + QQ_KEY_LENGTH];
+	gint bytes = 0;
+
+	bytes += qq_put32(src + bytes, uid);
+	bytes += qq_putdata(src + bytes, session_key, QQ_KEY_LENGTH);
+
+	qq_get_md5(session_md5, QQ_KEY_LENGTH, src, bytes);
+}
+
+/* process login reply which says OK */
+static gint8 process_login_ok(PurpleConnection *gc, guint8 *data, gint len)
+{
+	qq_data *qd;
+	gint bytes;
+
+	guint8 ret;
+	guint32 uid;
+	struct in_addr ip;
+	guint16 port;
+	struct tm *tm_local;
+
+	qd = (qq_data *) gc->proto_data;
+	/* qq_show_packet("Login reply", data, len); */
+
+	if (len < 139) {
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
+				_("Can not decrypt get server reply"));
+		return QQ_LOGIN_REPLY_ERR;
+	}
+
+	bytes = 0;
+	bytes += qq_get8(&ret, data + bytes);
+	bytes += qq_getdata(qd->session_key, sizeof(qd->session_key), data + bytes);
+	get_session_md5(qd->session_md5, qd->uid, qd->session_key);
+	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);
+	}
+	bytes += qq_getIP(&qd->my_ip, data + bytes);
+	bytes += qq_get16(&qd->my_port, data + bytes);
+	purple_debug_info("QQ", "Internet IP: %s, %d\n", inet_ntoa(qd->my_ip), qd->my_port);
+
+	bytes += qq_getIP(&qd->my_local_ip, data + bytes);
+	bytes += qq_get16(&qd->my_local_port, data + bytes);
+	purple_debug_info("QQ", "Local IP: %s, %d\n", inet_ntoa(qd->my_local_ip), qd->my_local_port);
+
+	bytes += qq_getime(&qd->login_time, data + bytes);
+	tm_local = localtime(&qd->login_time);
+	purple_debug_info("QQ", "Login time: %d-%d-%d, %d:%d:%d\n",
+			(1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday,
+			tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec);
+	/* skip unknown 2 bytes, 0x(03 0a) */
+	bytes += 2;
+	/* skip unknown 24 bytes, maybe token to access Qun shared files */
+	bytes += 24;
+	/* unknow ip and port */
+	bytes += qq_getIP(&ip, data + bytes);
+	bytes += qq_get16(&port, data + bytes);
+	purple_debug_info("QQ", "Unknow IP: %s, %d\n", inet_ntoa(ip), port);
+	/* unknow ip and port */
+	bytes += qq_getIP(&ip, data + bytes);
+	bytes += qq_get16(&port, data + bytes);
+	purple_debug_info("QQ", "Unknow IP: %s, %d\n", inet_ntoa(ip), port);
+	/* unknown 4 bytes, 0x(00 81 00 00)*/
+	bytes += 4;
+	/* skip unknown 32 bytes, maybe key to access QQ Home */
+	bytes += 32;
+	/* skip unknown 16 bytes, 0x(00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 00) */
+	bytes += 16;
+	/* time */
+	bytes += qq_getime(&qd->last_login_time[0], data + bytes);
+	tm_local = localtime(&qd->last_login_time[0]);
+	purple_debug_info("QQ", "Last login time: %d-%d-%d, %d:%d:%d\n",
+			(1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday,
+			tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec);
+	/* unknow time */
+	g_return_val_if_fail(sizeof(qd->last_login_time) / sizeof(time_t) > 1, QQ_LOGIN_REPLY_OK);
+	bytes += qq_getime(&qd->last_login_time[1], data + bytes);
+	tm_local = localtime(&qd->last_login_time[1]);
+	purple_debug_info("QQ", "Time: %d-%d-%d, %d:%d:%d\n",
+			(1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday,
+			tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec);
+
+	g_return_val_if_fail(sizeof(qd->last_login_time) / sizeof(time_t) > 2, QQ_LOGIN_REPLY_OK);
+	bytes += qq_getime(&qd->last_login_time[2], data + bytes);
+	tm_local = localtime(&qd->last_login_time[2]);
+	purple_debug_info("QQ", "Time: %d-%d-%d, %d:%d:%d\n",
+			(1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday,
+			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);
+	}
+	return QQ_LOGIN_REPLY_OK;
+}
+
+/* process login reply packet which includes redirected new server address */
+static gint8 process_login_redirect(PurpleConnection *gc, guint8 *data, gint len)
+{
+	qq_data *qd;
+	gint bytes;
+	struct {
+		guint8 result;
+		guint32 uid;
+		struct in_addr new_server_ip;
+		guint16 new_server_port;
+	} packet;
+
+
+	if (len < 11) {
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
+				_("Can not decrypt get server reply"));
+		return QQ_LOGIN_REPLY_ERR;
+	}
+
+	qd = (qq_data *) gc->proto_data;
+	bytes = 0;
+	/* 000-000: reply code */
+	bytes += qq_get8(&packet.result, data + bytes);
+	/* 001-004: login uid */
+	bytes += qq_get32(&packet.uid, data + bytes);
+	/* 005-008: redirected new server IP */
+	bytes += qq_getIP(&packet.new_server_ip, data + bytes);
+	/* 009-010: redirected new server port */
+	bytes += qq_get16(&packet.new_server_port, data + bytes);
+
+	if (len > 11) {
+		purple_debug_error("QQ", "Login redirect more than expected %d bytes, read %d bytes\n", 11, bytes);
+	}
+
+	/* redirect to new server, do not disconnect or connect here
+	 * those connect should be called at packet_process */
+	qd->redirect_ip.s_addr = packet.new_server_ip.s_addr;
+	qd->redirect_port = packet.new_server_port;
+	return QQ_LOGIN_REPLY_REDIRECT;
+}
+
+/* request before login */
+void qq_request_token(PurpleConnection *gc)
+{
+	qq_data *qd;
+	guint8 buf[16] = {0};
+	gint bytes = 0;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	bytes += qq_put8(buf + bytes, 0);
+
+	qd->send_seq++;
+	qq_send_cmd_encrypted(gc, QQ_CMD_TOKEN, qd->send_seq, buf, bytes, TRUE);
+}
+
+/* send login packet to QQ server */
+void qq_request_login(PurpleConnection *gc)
+{
+	qq_data *qd;
+	guint8 *buf, *raw_data;
+	gint bytes;
+	guint8 *encrypted;
+	gint encrypted_len;
+
+	/* for QQ 2005? copy from lumaqq */
+	static const guint8 login_23_51[29] = {
+			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			0x00, 0x00, 0x00, 0x00, 0x86, 0xcc, 0x4c, 0x35,
+			0x2c, 0xd3, 0x73, 0x6c, 0x14, 0xf6, 0xf6, 0xaf,
+			0xc3, 0xfa, 0x33, 0xa4, 0x01
+	};
+
+	static const guint8 login_53_68[16] = {
+ 			0x8D, 0x8B, 0xFA, 0xEC, 0xD5, 0x52, 0x17, 0x4A,
+ 			0x86, 0xF9, 0xA7, 0x75, 0xE6, 0x32, 0xD1, 0x6D
+	};
+
+	static const guint8 login_100_bytes[100] = {
+		0x40, 0x0B, 0x04, 0x02, 0x00, 0x01, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x03, 0x09, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0x01, 0xE9, 0x03, 0x01,
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF3, 0x03,
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xED,
+		0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+		0xEC, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x03, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x03, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x01, 0xEE, 0x03, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x01, 0xEF, 0x03, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0x01, 0xEB, 0x03, 0x00,
+		0x00, 0x00, 0x00, 0x00
+	};
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0);
+
+	raw_data = g_newa(guint8, MAX_PACKET_SIZE - 16);
+	memset(raw_data, 0, MAX_PACKET_SIZE - 16);
+
+	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 16 bytes more */
+
+	bytes = 0;
+	/* now generate the encrypted data
+	 * 000-015 use password_twice_md5 as key to encrypt empty string */
+	encrypted_len = qq_encrypt(encrypted, (guint8 *) "", 0, qd->ld.pwd_twice_md5);
+	g_return_if_fail(encrypted_len == 16);
+	bytes += qq_putdata(raw_data + bytes, encrypted, encrypted_len);
+
+	/* 016-016 */
+	bytes += qq_put8(raw_data + bytes, 0x00);
+	/* 017-020, used to be IP, now zero */
+	bytes += qq_put32(raw_data + bytes, 0x00000000);
+	/* 021-022, used to be port, now zero */
+	bytes += qq_put16(raw_data + bytes, 0x0000);
+	/* 023-051, fixed value, unknown */
+	bytes += qq_putdata(raw_data + bytes, login_23_51, 29);
+	/* 052-052, login mode */
+	bytes += qq_put8(raw_data + bytes, qd->login_mode);
+	/* 053-068, fixed value, maybe related to per machine */
+	bytes += qq_putdata(raw_data + bytes, login_53_68, 16);
+	/* 069, login token length */
+	bytes += qq_put8(raw_data + bytes, qd->ld.token_len);
+	/* 070-093, login token, normally 24 bytes */
+	bytes += qq_putdata(raw_data + bytes, qd->ld.token, qd->ld.token_len);
+	/* 100 bytes unknown */
+	bytes += qq_putdata(raw_data + bytes, login_100_bytes, 100);
+	/* all zero left */
+	memset(raw_data + bytes, 0, 416 - bytes);
+	bytes = 416;
+
+	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.random_key);
+
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+	memset(buf, 0, MAX_PACKET_SIZE);
+	bytes = 0;
+	bytes += qq_putdata(buf + bytes, qd->ld.random_key, QQ_KEY_LENGTH);
+	bytes += qq_putdata(buf + bytes, encrypted, encrypted_len);
+
+	qd->send_seq++;
+	qq_send_cmd_encrypted(gc, QQ_CMD_LOGIN, qd->send_seq, buf, bytes, TRUE);
+}
+
+guint8 qq_process_token(PurpleConnection *gc, guint8 *buf, gint buf_len)
+{
+	qq_data *qd;
+	gint bytes;
+	guint8 ret;
+	guint8 token_len;
+	gchar *msg;
+
+	g_return_val_if_fail(buf != NULL && buf_len != 0, QQ_LOGIN_REPLY_ERR);
+
+	g_return_val_if_fail(gc != NULL  && gc->proto_data != NULL, QQ_LOGIN_REPLY_ERR);
+	qd = (qq_data *) gc->proto_data;
+
+	bytes = 0;
+	bytes += qq_get8(&ret, buf + bytes);
+	bytes += qq_get8(&token_len, buf + bytes);
+
+	if (ret != QQ_LOGIN_REPLY_OK) {
+		qq_show_packet("Failed requesting token", buf, buf_len);
+
+		msg = g_strdup_printf( _("Failed requesting token, 0x%02X"), ret );
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
+				msg);
+		g_free(msg);
+		return QQ_LOGIN_REPLY_ERR;
+	}
+
+	if (bytes + token_len < buf_len) {
+		msg = g_strdup_printf( _("Invalid token len, %d"), token_len);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
+				msg);
+		g_free(msg);
+		return QQ_LOGIN_REPLY_ERR;
+	}
+
+	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);
+
+	if (qd->ld.token != NULL) {
+		g_free(qd->ld.token);
+		qd->ld.token = NULL;
+		qd->ld.token_len = 0;
+	}
+	qd->ld.token = g_new0(guint8, token_len);
+	qd->ld.token_len = token_len;
+	g_memmove(qd->ld.token, buf + 2, qd->ld.token_len);
+	return ret;
+}
+
+/* send logout packets to QQ server */
+void qq_request_logout(PurpleConnection *gc)
+{
+	gint i;
+	qq_data *qd;
+
+	qd = (qq_data *) gc->proto_data;
+	for (i = 0; i < 4; i++)
+		qq_send_cmd(gc, QQ_CMD_LOGOUT, qd->ld.pwd_twice_md5, QQ_KEY_LENGTH);
+
+	qd->is_login = FALSE;	/* update login status AFTER sending logout packets */
+}
 
 /* for QQ 2003iii 0117, fixed value */
 /* static const guint8 login_23_51[29] = {
@@ -61,37 +374,6 @@
 };
 */
 
-/* for QQ 2005? copy from lumaqq */
-/* FIXME: change to guint8 */
-static const guint8 login_23_51[29] = {
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x86, 0xcc, 0x4c, 0x35,
-	0x2c, 0xd3, 0x73, 0x6c, 0x14, 0xf6, 0xf6, 0xaf,
-	0xc3, 0xfa, 0x33, 0xa4, 0x01
-};
-
-static const guint8 login_53_68[16] = {
- 	0x8D, 0x8B, 0xFA, 0xEC, 0xD5, 0x52, 0x17, 0x4A,
- 	0x86, 0xF9, 0xA7, 0x75, 0xE6, 0x32, 0xD1, 0x6D
-};
-
-static const guint8 login_100_bytes[100] = {
-	0x40, 0x0B, 0x04, 0x02, 0x00, 0x01, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x03, 0x09, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x01, 0xE9, 0x03, 0x01,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF3, 0x03,
-	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xED,
-	0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
-	0xEC, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x03, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x03, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x01, 0xEE, 0x03, 0x00, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x01, 0xEF, 0x03, 0x00, 0x00,
-	0x00, 0x00, 0x00, 0x00, 0x01, 0xEB, 0x03, 0x00,
-	0x00, 0x00, 0x00, 0x00
-};
-
-
 /* fixed value, not affected by version, or mac address */
 /*
 static const guint8 login_53_68[16] = {
@@ -100,310 +382,16 @@
 };
 */
 
-
-typedef struct _qq_login_reply_ok qq_login_reply_ok_packet;
-typedef struct _qq_login_reply_redirect qq_login_reply_redirect_packet;
-
-struct _qq_login_reply_ok {
-	guint8 result;
-	guint8 session_key[QQ_KEY_LENGTH];
-	guint32 uid;
-	struct in_addr client_ip;	/* those detected by server */
-	guint16 client_port;
-	struct in_addr server_ip;
-	guint16 server_port;
-	time_t login_time;
-	guint8 unknown1[26];
-	struct in_addr unknown_server1_ip;
-	guint16 unknown_server1_port;
-	struct in_addr unknown_server2_ip;
-	guint16 unknown_server2_port;
-	guint16 unknown2;	/* 0x0001 */
-	guint16 unknown3;	/* 0x0000 */
-	guint8 unknown4[32];
-	guint8 unknown5[12];
-	struct in_addr last_client_ip;
-	time_t last_login_time;
-	guint8 unknown6[8];
-};
-
-struct _qq_login_reply_redirect {
-	guint8 result;
-	guint32 uid;
-	struct in_addr new_server_ip;
-	guint16 new_server_port;
-};
-
-/* generate a md5 key using uid and session_key */
-static void get_session_md5(guint8 *session_md5, guint32 uid, guint8 *session_key)
-{
-	guint8 src[QQ_KEY_LENGTH + QQ_KEY_LENGTH];
-	gint bytes = 0;
-
-	bytes += qq_put32(src + bytes, uid);
-	bytes += qq_putdata(src + bytes, session_key, QQ_KEY_LENGTH);
-
-	qq_get_md5(session_md5, QQ_KEY_LENGTH, src, bytes);
-}
-
-/* process login reply which says OK */
-static gint8 process_login_ok(PurpleConnection *gc, guint8 *data, gint len)
-{
-	gint bytes;
-	qq_data *qd;
-	qq_login_reply_ok_packet lrop;
-
-	qd = (qq_data *) gc->proto_data;
-	/* FIXME, check QQ_LOGIN_REPLY_OK_PACKET_LEN here */
-	bytes = 0;
-
-	/* 000-000: reply code */
-	bytes += qq_get8(&lrop.result, data + bytes);
-	/* 001-016: session key */
-	bytes += qq_getdata(lrop.session_key, sizeof(lrop.session_key), data + bytes);
-	purple_debug_info("QQ", "Got session_key\n");
-	/* 017-020: login uid */
-	bytes += qq_get32(&lrop.uid, data + bytes);
-	/* 021-024: server detected user public IP */
-	bytes += qq_getIP(&lrop.client_ip, data + bytes);
-	/* 025-026: server detected user port */
-	bytes += qq_get16(&lrop.client_port, data + bytes);
-	/* 027-030: server detected itself ip 127.0.0.1 ? */
-	bytes += qq_getIP(&lrop.server_ip, data + bytes);
-	/* 031-032: server listening port */
-	bytes += qq_get16(&lrop.server_port, data + bytes);
-	/* 033-036: login time for current session */
-	bytes += qq_getime(&lrop.login_time, data + bytes);
-	/* 037-062: 26 bytes, unknown */
-	bytes += qq_getdata((guint8 *) &lrop.unknown1, 26, data + bytes);
-	/* 063-066: unknown server1 ip address */
-	bytes += qq_getIP(&lrop.unknown_server1_ip, data + bytes);
-	/* 067-068: unknown server1 port */
-	bytes += qq_get16(&lrop.unknown_server1_port, data + bytes);
-	/* 069-072: unknown server2 ip address */
-	bytes += qq_getIP(&lrop.unknown_server2_ip, data + bytes);
-	/* 073-074: unknown server2 port */
-	bytes += qq_get16(&lrop.unknown_server2_port, data + bytes);
-	/* 075-076: 2 bytes unknown */
-	bytes += qq_get16(&lrop.unknown2, data + bytes);
-	/* 077-078: 2 bytes unknown */
-	bytes += qq_get16(&lrop.unknown3, data + bytes);
-	/* 079-110: 32 bytes unknown */
-	bytes += qq_getdata((guint8 *) &lrop.unknown4, 32, data + bytes);
-	/* 111-122: 12 bytes unknown */
-	bytes += qq_getdata((guint8 *) &lrop.unknown5, 12, data + bytes);
-	/* 123-126: login IP of last session */
-	bytes += qq_getIP(&lrop.last_client_ip, data + bytes);
-	/* 127-130: login time of last session */
-	bytes += qq_getime(&lrop.last_login_time, data + bytes);
-	/* 131-138: 8 bytes unknown */
-	bytes += qq_getdata((guint8 *) &lrop.unknown6, 8, data + bytes);
-
-	if (bytes != QQ_LOGIN_REPLY_OK_PACKET_LEN) {	/* fail parsing login info */
-		purple_debug_warning("QQ",
-			   "Fail parsing login info, expect %d bytes, read %d bytes\n",
-			   QQ_LOGIN_REPLY_OK_PACKET_LEN, bytes);
-	}			/* but we still go on as login OK */
-
-	memcpy(qd->session_key, lrop.session_key, sizeof(qd->session_key));
-	get_session_md5(qd->session_md5, qd->uid, qd->session_key);
-
-	qd->my_ip.s_addr = lrop.client_ip.s_addr;
-
-	qd->my_port = lrop.client_port;
-	qd->login_time = lrop.login_time;
-	qd->last_login_time = lrop.last_login_time;
-	qd->last_login_ip = g_strdup( inet_ntoa(lrop.last_client_ip) );
-
-	return QQ_LOGIN_REPLY_OK;
-}
-
-/* process login reply packet which includes redirected new server address */
-static gint8 process_login_redirect(PurpleConnection *gc, guint8 *data, gint len)
-{
-	qq_data *qd;
-	gint bytes;
-	qq_login_reply_redirect_packet lrrp;
-
-	qd = (qq_data *) gc->proto_data;
-	bytes = 0;
-	/* 000-000: reply code */
-	bytes += qq_get8(&lrrp.result, data + bytes);
-	/* 001-004: login uid */
-	bytes += qq_get32(&lrrp.uid, data + bytes);
-	/* 005-008: redirected new server IP */
-	bytes += qq_getIP(&lrrp.new_server_ip, data + bytes);
-	/* 009-010: redirected new server port */
-	bytes += qq_get16(&lrrp.new_server_port, data + bytes);
-
-	if (bytes != QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN) {
-		purple_debug_error("QQ",
-			   "Fail parsing login redirect packet, expect %d bytes, read %d bytes\n",
-			   QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN, bytes);
-		return QQ_LOGIN_REPLY_ERR_MISC;
-	}
-
-	/* redirect to new server, do not disconnect or connect here
-	 * those connect should be called at packet_process */
-	qd->redirect_ip.s_addr = lrrp.new_server_ip.s_addr;
-	qd->redirect_port = lrrp.new_server_port;
-	return QQ_LOGIN_REPLY_REDIRECT;
-}
-
-/* request before login */
-void qq_send_packet_token(PurpleConnection *gc)
-{
-	qq_data *qd;
-	guint8 buf[16] = {0};
-	gint bytes = 0;
-
-	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
-	qd = (qq_data *) gc->proto_data;
-
-	bytes += qq_put8(buf + bytes, 0);
-
-	qd->send_seq++;
-	qq_send_cmd_encrypted(gc, QQ_CMD_TOKEN, qd->send_seq, buf, bytes, TRUE);
-}
-
-/* send login packet to QQ server */
-void qq_send_packet_login(PurpleConnection *gc)
-{
-	qq_data *qd;
-	guint8 *buf, *raw_data;
-	gint bytes;
-	guint8 *encrypted_data;
-	gint encrypted_len;
-
-	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
-	qd = (qq_data *) gc->proto_data;
-
-	g_return_if_fail(qd->token != NULL && qd->token_len > 0);
-
-#ifdef DEBUG
-	memset(qd->inikey, 0x01, sizeof(qd->inikey));
-#else
-	for (bytes = 0; bytes < sizeof(qd->inikey); bytes++)	{
-		qd->inikey[bytes] = (guint8) (rand() & 0xff);
-	}
-#endif
-
-	raw_data = g_newa(guint8, QQ_LOGIN_DATA_LENGTH);
-	memset(raw_data, 0, QQ_LOGIN_DATA_LENGTH);
-
-	encrypted_data = g_newa(guint8, QQ_LOGIN_DATA_LENGTH + 16);	/* 16 bytes more */
-
-	bytes = 0;
-	/* now generate the encrypted data
-	 * 000-015 use password_twice_md5 as key to encrypt empty string */
-	encrypted_len = qq_encrypt(raw_data + bytes, (guint8 *) "", 0, qd->password_twice_md5);
-	g_return_if_fail(encrypted_len == 16);
-	bytes += encrypted_len;
-
-	/* 016-016 */
-	bytes += qq_put8(raw_data + bytes, 0x00);
-	/* 017-020, used to be IP, now zero */
-	bytes += qq_put32(raw_data + bytes, 0x00000000);
-	/* 021-022, used to be port, now zero */
-	bytes += qq_put16(raw_data + bytes, 0x0000);
-	/* 023-051, fixed value, unknown */
-	bytes += qq_putdata(raw_data + bytes, login_23_51, 29);
-	/* 052-052, login mode */
-	bytes += qq_put8(raw_data + bytes, qd->login_mode);
-	/* 053-068, fixed value, maybe related to per machine */
-	bytes += qq_putdata(raw_data + bytes, login_53_68, 16);
-	/* 069, login token length */
-	bytes += qq_put8(raw_data + bytes, qd->token_len);
-	/* 070-093, login token, normally 24 bytes */
-	bytes += qq_putdata(raw_data + bytes, qd->token, qd->token_len);
-	/* 100 bytes unknown */
-	bytes += qq_putdata(raw_data + bytes, login_100_bytes, 100);
-	/* all zero left */
-
-	encrypted_len = qq_encrypt(encrypted_data, raw_data, QQ_LOGIN_DATA_LENGTH, qd->inikey);
-
-	buf = g_newa(guint8, MAX_PACKET_SIZE);
-	memset(buf, 0, MAX_PACKET_SIZE);
-	bytes = 0;
-	bytes += qq_putdata(buf + bytes, qd->inikey, QQ_KEY_LENGTH);
-	bytes += qq_putdata(buf + bytes, encrypted_data, encrypted_len);
-
-	qd->send_seq++;
-	qq_send_cmd_encrypted(gc, QQ_CMD_LOGIN, qd->send_seq, buf, bytes, TRUE);
-}
-
-guint8 qq_process_token_reply(PurpleConnection *gc, guint8 *buf, gint buf_len)
-{
-	qq_data *qd;
-	guint8 ret;
-	int token_len;
-	gchar *error_msg;
-
-	g_return_val_if_fail(buf != NULL && buf_len != 0, -1);
-
-	g_return_val_if_fail(gc != NULL  && gc->proto_data != NULL, -1);
-	qd = (qq_data *) gc->proto_data;
-
-	ret = buf[0];
-
-	if (ret != QQ_TOKEN_REPLY_OK) {
-		purple_debug_error("QQ", "Failed to request token: %d\n", buf[0]);
-		qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ",
-				buf, buf_len,
-				">>> [default] decrypt and dump");
-		error_msg = try_dump_as_gbk(buf, buf_len);
-		if (error_msg == NULL) {
-				error_msg = g_strdup_printf( _("Invalid token reply code, 0x%02X"), ret);
-		}
-		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_msg);
-		g_free(error_msg);
-		return ret;
-	}
-
-	token_len = buf_len-2;
-	if (token_len <= 0) {
-		error_msg = g_strdup_printf( _("Invalid token len, %d"), token_len);
-		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_msg);
-		g_free(error_msg);
-		return -1;
-	}
-
-	if (buf[1] != token_len) {
-		purple_debug_info("QQ",
-				"Invalid token len. Packet specifies length of %d, actual length is %d\n", buf[1], buf_len-2);
-	}
-	qq_hex_dump(PURPLE_DEBUG_INFO, "QQ",
-			buf+2, token_len,
-			"<<< got a token -> [default] decrypt and dump");
-
-	qd->token = g_new0(guint8, token_len);
-	qd->token_len = token_len;
-	g_memmove(qd->token, buf + 2, qd->token_len);
-	return ret;
-}
-
-/* send logout packets to QQ server */
-void qq_send_packet_logout(PurpleConnection *gc)
-{
-	gint i;
-	qq_data *qd;
-
-	qd = (qq_data *) gc->proto_data;
-	for (i = 0; i < 4; i++)
-		qq_send_cmd(gc, QQ_CMD_LOGOUT, qd->password_twice_md5, QQ_KEY_LENGTH);
-
-	qd->is_login = FALSE;	/* update login status AFTER sending logout packets */
-}
-
 /* process the login reply packet */
-guint8 qq_process_login_reply( PurpleConnection *gc, guint8 *data, gint data_len)
+guint8 qq_process_login( PurpleConnection *gc, guint8 *data, gint data_len)
 {
 	qq_data *qd;
 	guint8 ret = data[0];
-	gchar *server_reply, *server_reply_utf8;
-	gchar *error_msg;
+	gchar *msg, *msg_utf8;
+	gchar *error;
+	PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
 
-	g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR_MISC);
+	g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR);
 
 	qd = (qq_data *) gc->proto_data;
 
@@ -415,64 +403,46 @@
 			purple_debug_info("QQ", "Redirect new server\n");
 			return process_login_redirect(gc, data, data_len);
 
-		case QQ_LOGIN_REPLY_REDIRECT_EX:
-			purple_debug_error("QQ", "Extend redirect new server, not supported yet\n");
-			error_msg = g_strdup( _("Unable login for not support Redirect_EX now") );
-			return QQ_LOGIN_REPLY_REDIRECT_EX;
-
-		case QQ_LOGIN_REPLY_ERR_PWD:
-			server_reply = g_strndup((gchar *)data + 1, data_len - 1);
-			server_reply_utf8 = qq_to_utf8(server_reply, QQ_CHARSET_DEFAULT);
-
-			purple_debug_error("QQ", "Error password: %s\n", server_reply_utf8);
-			error_msg = g_strdup_printf( _("Error password: %s"), server_reply_utf8);
-
-			g_free(server_reply);
-			g_free(server_reply_utf8);
-
+		case 0x0A:		/* extend redirect used in QQ2006 */
+			error = g_strdup( _("Not support Redirect_EX now") );
+			reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
+			break;
+		case 0x05:		/* invalid password */
 			if (!purple_account_get_remember_password(gc->account)) {
 				purple_account_set_password(gc->account, NULL);
 			}
-
-			purple_connection_error_reason(gc,
-				PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, error_msg);
-			g_free(error_msg);
-
-			return QQ_LOGIN_REPLY_ERR_PWD;
-
-		case QQ_LOGIN_REPLY_NEED_REACTIVE:
-			server_reply = g_strndup((gchar *)data + 1, data_len - 1);
-			server_reply_utf8 = qq_to_utf8(server_reply, QQ_CHARSET_DEFAULT);
-
-			purple_debug_error("QQ", "Need active: %s\n", server_reply_utf8);
-			error_msg = g_strdup_printf( _("Need active: %s"), server_reply_utf8);
-
-			g_free(server_reply);
-			g_free(server_reply_utf8);
+			error = g_strdup( _("Error password"));
+			reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
+			break;
+		case 0x06:		/* need activation */
+			error = g_strdup( _("Need active"));
+			reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
 			break;
 
 		default:
-			purple_debug_error("QQ",
-				"Unable login for unknow reply code 0x%02X\n", data[0]);
-			qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ",
-				data, data_len,
-				">>> [default] decrypt and dump");
-			error_msg = try_dump_as_gbk(data, data_len);
-			if (error_msg == NULL) {
-				error_msg = g_strdup_printf(
-					_("Unable login for unknow reply code 0x%02X"), data[0] );
-			}
+			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)"),
+						ret );
+			reason = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
 			break;
 	}
 
-	purple_connection_error_reason(gc,
-		PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_msg);
-	g_free(error_msg);
-	return ret;
+	msg = g_strndup((gchar *)data + 1, data_len - 1);
+	msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT);
+
+	purple_debug_error("QQ", "%s: %s\n", error, msg_utf8);
+	purple_connection_error_reason(gc, reason, msg_utf8);
+
+	g_free(error);
+	g_free(msg);
+	g_free(msg_utf8);
+	return QQ_LOGIN_REPLY_ERR;
 }
 
 /* send keep-alive packet to QQ server (it is a heart-beat) */
-void qq_send_packet_keep_alive(PurpleConnection *gc)
+void qq_request_keep_alive(PurpleConnection *gc)
 {
 	qq_data *qd;
 	guint8 raw_data[16] = {0};
@@ -484,11 +454,10 @@
 	 * with this command, server return the same result including
 	 * the amount of online QQ users, my ip and port */
 	bytes += qq_put32(raw_data + bytes, qd->uid);
-
-	qq_send_cmd(gc, QQ_CMD_KEEP_ALIVE, raw_data, 4);
+	qq_send_cmd(gc, QQ_CMD_KEEP_ALIVE, raw_data, bytes);
 }
 
-/* parse the return of keep-alive packet, it includes some system information */
+/* parse the return ofqq_process_keep_alive keep-alive packet, it includes some system information */
 gboolean qq_process_keep_alive(guint8 *data, gint data_len, PurpleConnection *gc)
 {
 	qq_data *qd;
@@ -505,9 +474,10 @@
 			return TRUE;
 
 	/* segments[0] and segment[1] are all 0x30 ("0") */
-	qd->total_online = strtol(segments[2], NULL, 10);
-	if(0 == qd->total_online) {
-		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+	qd->online_total = strtol(segments[2], NULL, 10);
+	if(0 == qd->online_total) {
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 				_("Keep alive error"));
 	}
 	qd->my_ip.s_addr = inet_addr(segments[3]);
@@ -519,3 +489,998 @@
 	g_strfreev(segments);
 	return TRUE;
 }
+
+void qq_request_keep_alive_2007(PurpleConnection *gc)
+{
+	qq_data *qd;
+	guint8 raw_data[32] = {0};
+	gint bytes= 0;
+	gchar *uid_str;
+
+	qd = (qq_data *) gc->proto_data;
+
+	/* 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);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)uid_str, strlen(uid_str));
+	qq_send_cmd(gc, QQ_CMD_KEEP_ALIVE, raw_data, bytes);
+
+	g_free(uid_str);
+}
+
+gboolean qq_process_keep_alive_2007(guint8 *data, gint data_len, PurpleConnection *gc)
+{
+	qq_data *qd;
+	gint bytes= 0;
+	guint8 ret;
+
+	g_return_val_if_fail(data != NULL && data_len != 0, FALSE);
+
+	qd = (qq_data *) gc->proto_data;
+
+	/* 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);
+	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);
+	return TRUE;
+}
+
+void qq_request_keep_alive_2008(PurpleConnection *gc)
+{
+	qq_data *qd;
+	guint8 raw_data[16] = {0};
+	gint bytes= 0;
+
+	qd = (qq_data *) gc->proto_data;
+
+	/* 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 */
+	bytes += qq_put32(raw_data + bytes, qd->uid);
+	bytes += qq_putime(raw_data + bytes, &qd->login_time);
+	qq_send_cmd(gc, QQ_CMD_KEEP_ALIVE, raw_data, bytes);
+}
+
+gboolean qq_process_keep_alive_2008(guint8 *data, gint data_len, PurpleConnection *gc)
+{
+	qq_data *qd;
+	gint bytes= 0;
+	guint8 ret;
+	time_t server_time;
+	struct tm *tm_local;
+
+	g_return_val_if_fail(data != NULL && data_len != 0, FALSE);
+
+	qd = (qq_data *) gc->proto_data;
+
+	/* 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);
+	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);
+	/* skip 2 byytes, 0x(00 3c) */
+	bytes += 2;
+	bytes += qq_getime(&server_time, data + bytes);
+	/* skip 5 bytes, all are 0 */
+
+	purple_debug_info("QQ", "keep alive, %s:%d\n",
+		inet_ntoa(qd->my_ip), qd->my_port);
+
+	tm_local = localtime(&server_time);
+	purple_debug_info("QQ", "Server time: %d-%d-%d, %d:%d:%d\n",
+			(1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday,
+			tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec);
+	return TRUE;
+}
+
+/* For QQ2007/2008 */
+void qq_request_get_server(PurpleConnection *gc)
+{
+	qq_data *qd;
+	guint8 *buf, *raw_data;
+	gint bytes;
+	guint8 *encrypted;
+	gint encrypted_len;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	raw_data = g_newa(guint8, 128);
+	memset(raw_data, 0, 128);
+
+	encrypted = g_newa(guint8, 128 + 16);	/* 16 bytes more */
+
+	bytes = 0;
+	if (qd->redirect == NULL) {
+		/* first packet to get server */
+		qd->redirect_len = 15;
+		qd->redirect = g_realloc(qd->redirect, qd->redirect_len);
+		memset(qd->redirect, 0, qd->redirect_len);
+	}
+	bytes += qq_putdata(raw_data + bytes, qd->redirect, qd->redirect_len);
+
+	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.random_key);
+
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+	memset(buf, 0, MAX_PACKET_SIZE);
+	bytes = 0;
+	bytes += qq_putdata(buf + bytes, qd->ld.random_key, QQ_KEY_LENGTH);
+	bytes += qq_putdata(buf + bytes, encrypted, encrypted_len);
+
+	qd->send_seq++;
+	qq_send_cmd_encrypted(gc, QQ_CMD_GET_SERVER, qd->send_seq, buf, bytes, TRUE);
+}
+
+guint16 qq_process_get_server(PurpleConnection *gc, guint8 *data, gint data_len)
+{
+	qq_data *qd;
+	gint bytes;
+	guint16 ret;
+
+	g_return_val_if_fail (gc != NULL && gc->proto_data != NULL, QQ_LOGIN_REPLY_ERR);
+	qd = (qq_data *) gc->proto_data;
+
+	g_return_val_if_fail (data != NULL, QQ_LOGIN_REPLY_ERR);
+
+	/* qq_show_packet("Get Server", data, data_len); */
+	bytes = 0;
+	bytes += qq_get16(&ret, data + bytes);
+	if (ret == 0) {
+		/* Notice: do not clear redirect_data here. It will be used in login*/
+		qd->redirect_ip.s_addr = 0;
+		return QQ_LOGIN_REPLY_OK;
+	}
+
+	if (data_len < 15) {
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
+				_("Can not decrypt get server reply"));
+		return QQ_LOGIN_REPLY_ERR;
+	}
+
+	qd->redirect_len = data_len;
+	qd->redirect = g_realloc(qd->redirect, qd->redirect_len);
+	qq_getdata(qd->redirect, qd->redirect_len, data);
+	/* qq_show_packet("Redirect to", qd->redirect, qd->redirect_len); */
+
+	qq_getIP(&qd->redirect_ip, data + 11);
+	purple_debug_info("QQ", "Get server %s\n", inet_ntoa(qd->redirect_ip));
+	return QQ_LOGIN_REPLY_REDIRECT;
+}
+
+void qq_request_token_ex(PurpleConnection *gc)
+{
+	qq_data *qd;
+	guint8 *buf, *raw_data;
+	gint bytes;
+	guint8 *encrypted;
+	gint encrypted_len;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0);
+
+	raw_data = g_newa(guint8, MAX_PACKET_SIZE - 16);
+	memset(raw_data, 0, MAX_PACKET_SIZE - 16);
+
+	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 16 bytes more */
+
+	bytes = 0;
+	bytes += qq_put8(raw_data + bytes, qd->ld.token_len);
+	bytes += qq_putdata(raw_data + bytes, qd->ld.token, qd->ld.token_len);
+	bytes += qq_put8(raw_data + bytes, 3); 		/* Subcommand */
+	bytes += qq_put16(raw_data + bytes, 5);
+	bytes += qq_put32(raw_data + bytes, 0);
+	bytes += qq_put8(raw_data + bytes, 0); 		/* fragment index */
+	bytes += qq_put16(raw_data + bytes, 0); 	/* captcha token */
+
+	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.random_key);
+
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+	memset(buf, 0, MAX_PACKET_SIZE);
+	bytes = 0;
+	bytes += qq_putdata(buf + bytes, qd->ld.random_key, QQ_KEY_LENGTH);
+	bytes += qq_putdata(buf + bytes, encrypted, encrypted_len);
+
+	qd->send_seq++;
+	qq_send_cmd_encrypted(gc, QQ_CMD_TOKEN_EX, qd->send_seq, buf, bytes, TRUE);
+}
+
+void qq_request_token_ex_next(PurpleConnection *gc)
+{
+	qq_data *qd;
+	guint8 *buf, *raw_data;
+	gint bytes;
+	guint8 *encrypted;
+	gint encrypted_len;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0);
+
+	raw_data = g_newa(guint8, MAX_PACKET_SIZE - 16);
+	memset(raw_data, 0, MAX_PACKET_SIZE - 16);
+
+	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 16 bytes more */
+
+	bytes = 0;
+	bytes += qq_put8(raw_data + bytes, qd->ld.token_len);
+	bytes += qq_putdata(raw_data + bytes, qd->ld.token, qd->ld.token_len);
+	bytes += qq_put8(raw_data + bytes, 3); 		/* Subcommand */
+	bytes += qq_put16(raw_data + bytes, 5);
+	bytes += qq_put32(raw_data + bytes, 0);
+	bytes += qq_put8(raw_data + bytes, qd->captcha.next_index); 		/* fragment index */
+	bytes += qq_put16(raw_data + bytes, qd->captcha.token_len); 	/* captcha token */
+	bytes += qq_putdata(raw_data + bytes, qd->captcha.token, qd->captcha.token_len);
+
+	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.random_key);
+
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+	memset(buf, 0, MAX_PACKET_SIZE);
+	bytes = 0;
+	bytes += qq_putdata(buf + bytes, qd->ld.random_key, QQ_KEY_LENGTH);
+	bytes += qq_putdata(buf + bytes, encrypted, encrypted_len);
+
+	qd->send_seq++;
+	qq_send_cmd_encrypted(gc, QQ_CMD_TOKEN_EX, qd->send_seq, buf, bytes, TRUE);
+
+	purple_connection_update_progress(gc, _("Requesting captcha ..."), 3, QQ_CONNECT_STEPS);
+}
+
+static void request_token_ex_code(PurpleConnection *gc,
+		guint8 *token, guint16 token_len, guint8 *code, guint16 code_len)
+{
+	qq_data *qd;
+	guint8 *buf, *raw_data;
+	gint bytes;
+	guint8 *encrypted;
+	gint encrypted_len;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0);
+	g_return_if_fail(code != NULL && code_len > 0);
+
+	raw_data = g_newa(guint8, MAX_PACKET_SIZE - 16);
+	memset(raw_data, 0, MAX_PACKET_SIZE - 16);
+
+	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 16 bytes more */
+
+	bytes = 0;
+	bytes += qq_put8(raw_data + bytes, qd->ld.token_len);
+	bytes += qq_putdata(raw_data + bytes, qd->ld.token, qd->ld.token_len);
+	bytes += qq_put8(raw_data + bytes, 4); 		/* Subcommand */
+	bytes += qq_put16(raw_data + bytes, 5);
+	bytes += qq_put32(raw_data + bytes, 0);
+	bytes += qq_put16(raw_data + bytes, code_len);
+	bytes += qq_putdata(raw_data + bytes, code, code_len);
+	bytes += qq_put16(raw_data + bytes, qd->ld.token_ex_len); 	/* login token ex */
+	bytes += qq_putdata(raw_data + bytes, qd->ld.token_ex, qd->ld.token_ex_len);
+
+	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.random_key);
+
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+	memset(buf, 0, MAX_PACKET_SIZE);
+	bytes = 0;
+	bytes += qq_putdata(buf + bytes, qd->ld.random_key, QQ_KEY_LENGTH);
+	bytes += qq_putdata(buf + bytes, encrypted, encrypted_len);
+
+	qd->send_seq++;
+	qq_send_cmd_encrypted(gc, QQ_CMD_TOKEN_EX, qd->send_seq, buf, bytes, TRUE);
+
+	purple_connection_update_progress(gc, _("Checking code of  captcha ..."), 3, QQ_CONNECT_STEPS);
+}
+
+typedef struct {
+	PurpleConnection *gc;
+	guint8 *token;
+	guint16 token_len;
+} qq_captcha_request;
+
+static void captcha_request_destory(qq_captcha_request *captcha_req)
+{
+	g_return_if_fail(captcha_req != NULL);
+	if (captcha_req->token) g_free(captcha_req->token);
+	g_free(captcha_req);
+}
+
+static void captcha_input_cancel_cb(qq_captcha_request *captcha_req,
+		PurpleRequestFields *fields)
+{
+	captcha_request_destory(captcha_req);
+
+	purple_connection_error_reason(captcha_req->gc,
+			PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
+			_("Failed captcha verify"));
+}
+
+static void captcha_input_ok_cb(qq_captcha_request *captcha_req,
+		PurpleRequestFields *fields)
+{
+	gchar *code;
+
+	g_return_if_fail(captcha_req != NULL && captcha_req->gc != NULL);
+
+	code = utf8_to_qq(
+			purple_request_fields_get_string(fields, "captcha_code"),
+			QQ_CHARSET_DEFAULT);
+
+	if (strlen(code) <= 0) {
+		captcha_input_cancel_cb(captcha_req, fields);
+		return;
+	}
+
+	request_token_ex_code(captcha_req->gc,
+			captcha_req->token, captcha_req->token_len,
+			(guint8 *)code, strlen(code));
+
+	captcha_request_destory(captcha_req);
+}
+
+void qq_captcha_input_dialog(PurpleConnection *gc,qq_captcha_data *captcha)
+{
+	PurpleAccount *account;
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *group;
+	PurpleRequestField *field;
+	qq_captcha_request *captcha_req;
+
+	g_return_if_fail(captcha->token != NULL && captcha->token_len > 0);
+	g_return_if_fail(captcha->data != NULL && captcha->data_len > 0);
+
+	captcha_req = g_new0(qq_captcha_request, 1);
+	captcha_req->gc = gc;
+	captcha_req->token = g_new0(guint8, captcha->token_len);
+	g_memmove(captcha_req->token, captcha->token, captcha->token_len);
+	captcha_req->token_len = captcha->token_len;
+
+	account = purple_connection_get_account(gc);
+
+	fields = purple_request_fields_new();
+	group = purple_request_field_group_new(NULL);
+	purple_request_fields_add_group(fields, group);
+
+	field = purple_request_field_image_new("captcha_img",
+			_("Captcha Image"), (char *)captcha->data, captcha->data_len);
+	purple_request_field_group_add_field(group, field);
+
+	field = purple_request_field_string_new("captcha_code",
+			_("Enter code"), "", FALSE);
+	purple_request_field_string_set_masked(field, FALSE);
+	purple_request_field_group_add_field(group, field);
+
+	purple_request_fields(account,
+		_("QQ Captcha Verifing"),
+		_("QQ Captcha Verifing"),
+		_("Please fill code according to image"),
+		fields,
+		_("OK"), G_CALLBACK(captcha_input_ok_cb),
+		_("Cancel"), G_CALLBACK(captcha_input_cancel_cb),
+		purple_connection_get_account(gc), NULL, NULL,
+		captcha_req);
+}
+
+guint8 qq_process_token_ex(PurpleConnection *gc, guint8 *data, gint data_len)
+{
+	qq_data *qd;
+	int bytes;
+	guint8 ret;
+	guint8 sub_cmd;
+	guint8 reply;
+	guint16 captcha_len;
+	guint8 curr_index;
+
+	g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR);
+
+	g_return_val_if_fail(gc != NULL  && gc->proto_data != NULL, QQ_LOGIN_REPLY_ERR);
+	qd = (qq_data *) gc->proto_data;
+
+	ret = data[0];
+
+	bytes = 0;
+	bytes += qq_get8(&sub_cmd, data + bytes); /* 03: ok; 04: need verifying */
+	bytes += 2;	/* 0x(00 05) */
+	bytes += qq_get8(&reply, data + bytes);
+
+	bytes += qq_get16(&(qd->ld.token_ex_len), data + bytes);
+	qd->ld.token_ex = g_realloc(qd->ld.token_ex, qd->ld.token_ex_len);
+	bytes += qq_getdata(qd->ld.token_ex, qd->ld.token_ex_len, data + bytes);
+	/* qq_show_packet("Get token ex", qd->ld.token_ex, qd->ld.token_ex_len); */
+
+	if(reply != 1)
+	{
+		purple_debug_info("QQ", "Captcha verified, result %d\n", reply);
+		return QQ_LOGIN_REPLY_OK;
+	}
+
+	bytes += qq_get16(&captcha_len, data + bytes);
+
+	qd->captcha.data = g_realloc(qd->captcha.data, qd->captcha.data_len + captcha_len);
+	bytes += qq_getdata(qd->captcha.data + qd->captcha.data_len, captcha_len, data + bytes);
+	qd->captcha.data_len += captcha_len;
+
+	bytes += qq_get8(&curr_index, data + bytes);
+	bytes += qq_get8(&qd->captcha.next_index, data + bytes);
+
+	bytes += qq_get16(&qd->captcha.token_len, data + bytes);
+	qd->captcha.token = g_realloc(qd->captcha.token, qd->captcha.token_len);
+	bytes += qq_getdata(qd->captcha.token, qd->captcha.token_len, data + bytes);
+	/* qq_show_packet("Get captcha token", qd->captcha.token, qd->captcha.token_len); */
+
+	purple_debug_info("QQ", "Request next captcha %d, new %d, total %d\n",
+			qd->captcha.next_index, captcha_len, qd->captcha.data_len);
+	if(qd->captcha.next_index > 0)
+	{
+		return QQ_LOGIN_REPLY_NEXT_TOKEN_EX;
+	}
+
+	return QQ_LOGIN_REPLY_CAPTCHA_DLG;
+}
+
+/* source copy from gg's common.c */
+static guint32 crc32_table[256];
+static int crc32_initialized = 0;
+
+static void crc32_make_table()
+{
+	guint32 h = 1;
+	unsigned int i, j;
+
+	memset(crc32_table, 0, sizeof(crc32_table));
+
+	for (i = 128; i; i >>= 1) {
+		h = (h >> 1) ^ ((h & 1) ? 0xedb88320L : 0);
+
+		for (j = 0; j < 256; j += 2 * i)
+			crc32_table[i + j] = crc32_table[j] ^ h;
+	}
+
+	crc32_initialized = 1;
+}
+
+static guint32 crc32(guint32 crc, const guint8 *buf, int len)
+{
+	if (!crc32_initialized)
+		crc32_make_table();
+
+	if (!buf || len < 0)
+		return crc;
+
+	crc ^= 0xffffffffL;
+
+	while (len--)
+		crc = (crc >> 8) ^ crc32_table[(crc ^ *buf++) & 0xff];
+
+	return crc ^ 0xffffffffL;
+}
+
+void qq_request_check_pwd(PurpleConnection *gc)
+{
+	qq_data *qd;
+	guint8 *buf, *raw_data;
+	gint bytes;
+	guint8 *encrypted;
+	gint encrypted_len;
+	static guint8 header[] = {
+			0x00, 0x5F, 0x00, 0x00, 0x08, 0x04, 0x01, 0xE0
+	};
+	static guint8 unknown[] = {
+			0xDB, 0xB9, 0xF3, 0x0B, 0xF9, 0x13, 0x87, 0xB2,
+			0xE6, 0x20, 0x43, 0xBE, 0x53, 0xCA, 0x65, 0x03
+	};
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	g_return_if_fail(qd->ld.token_ex != NULL && qd->ld.token_ex_len > 0);
+
+	raw_data = g_newa(guint8, MAX_PACKET_SIZE - 16);
+	memset(raw_data, 0, MAX_PACKET_SIZE - 16);
+
+	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 16 bytes more */
+
+	/* Encrypted password and put in encrypted */
+	bytes = 0;
+	bytes += qq_putdata(raw_data + bytes, qd->ld.pwd_md5, sizeof(qd->ld.pwd_md5));
+	bytes += qq_put16(raw_data + bytes, 0);
+	bytes += qq_put16(raw_data + bytes, rand() & 0xffff);
+
+	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.pwd_twice_md5);
+
+	/* create packet */
+	bytes = 0;
+	bytes += qq_putdata(raw_data + bytes, header, sizeof(header));
+	/* token get from qq_request_token_ex */
+	bytes += qq_put8(raw_data + bytes, (guint8)(qd->ld.token_ex_len & 0xff));
+	bytes += qq_putdata(raw_data + bytes, qd->ld.token_ex, qd->ld.token_ex_len);
+	/* password encrypted */
+	bytes += qq_put16(raw_data + bytes, encrypted_len);
+	bytes += qq_putdata(raw_data + bytes, encrypted, encrypted_len);
+	/* len of unknown + len of CRC32 */
+	bytes += qq_put16(raw_data + bytes, sizeof(unknown) + 4);
+	bytes += qq_putdata(raw_data + bytes, unknown, sizeof(unknown));
+	bytes += qq_put32(
+			raw_data + bytes, crc32(0xFFFFFFFF, unknown, sizeof(unknown)));
+
+	/* put length into first 2 bytes */
+	qq_put8(raw_data + 1, bytes - 2);
+
+	/* tail */
+	bytes += qq_put16(raw_data + bytes, 0x0003);
+	bytes += qq_put8(raw_data + bytes, 0);
+	bytes += qq_put8(raw_data + bytes, qd->ld.pwd_md5[1]);
+	bytes += qq_put8(raw_data + bytes, qd->ld.pwd_md5[2]);
+
+	/* qq_show_packet("Check password", raw_data, bytes); */
+	/* Encrypted by random key*/
+	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.random_key);
+
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+	memset(buf, 0, MAX_PACKET_SIZE);
+	bytes = 0;
+	bytes += qq_putdata(buf + bytes, qd->ld.random_key, QQ_KEY_LENGTH);
+	bytes += qq_putdata(buf + bytes, encrypted, encrypted_len);
+
+	qd->send_seq++;
+	qq_send_cmd_encrypted(gc, QQ_CMD_CHECK_PWD, qd->send_seq, buf, bytes, TRUE);
+}
+
+guint8 qq_process_check_pwd( PurpleConnection *gc, guint8 *data, gint data_len)
+{
+	qq_data *qd;
+	int bytes;
+	guint8 ret;
+	gchar *error = NULL;
+	guint16 unknow_token_len;
+	gchar *msg, *msg_utf8;
+	guint16 msg_len;
+	PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
+
+	g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR);
+
+	g_return_val_if_fail(gc != NULL  && gc->proto_data != NULL, QQ_LOGIN_REPLY_ERR);
+	qd = (qq_data *) gc->proto_data;
+
+	/* qq_show_packet("Check password reply", data, data_len); */
+
+	bytes = 0;
+	bytes += qq_get16(&unknow_token_len, data + bytes);	/* maybe total length */
+	bytes += qq_get8(&ret, data + bytes);
+	bytes += 4; /* 0x(00 00 6d b9) */
+	/* unknow_token_len may 0 when not reply ok*/
+	bytes += qq_get16(&unknow_token_len, data + bytes);	/* 0x0020 */
+	bytes += unknow_token_len;
+	bytes += qq_get16(&unknow_token_len, data + bytes);	/* 0x0020 */
+	bytes += unknow_token_len;
+
+	if (ret == 0) {
+		/* get login_token */
+		bytes += qq_get16(&qd->ld.login_token_len, data + bytes);
+		if (qd->ld.login_token != NULL) g_free(qd->ld.login_token);
+		qd->ld.login_token = g_new0(guint8, qd->ld.login_token_len);
+		bytes += qq_getdata(qd->ld.login_token, qd->ld.login_token_len, data + bytes);
+		/* qq_show_packet("Get login token", qd->ld.login_token, qd->ld.login_token_len); */
+
+		/* get login_key */
+		bytes += qq_getdata(qd->ld.login_key, sizeof(qd->ld.login_key), data + bytes);
+		/* qq_show_packet("Get login key", qd->ld.login_key, sizeof(qd->ld.login_key)); */
+		return QQ_LOGIN_REPLY_OK;
+	}
+
+	switch (ret)
+	{
+		case 0x34:		/* invalid password */
+			if (!purple_account_get_remember_password(gc->account)) {
+				purple_account_set_password(gc->account, NULL);
+			}
+			error = g_strdup(_("Error password"));
+			reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
+			break;
+		case 0x33:		/* need activation */
+		case 0x51:		/* need activation */
+			error = g_strdup(_("Need active"));
+			reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
+			break;
+		case 0xBF:		/* uid is not exist */
+			error = g_strdup(_("invalid user name"));
+			reason = PURPLE_CONNECTION_ERROR_INVALID_USERNAME;
+			break;
+		default:
+			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)"),
+						ret );
+			reason = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
+			break;
+	}
+
+	bytes += qq_get16(&msg_len, data + bytes);
+
+	msg = g_strndup((gchar *)data + bytes, msg_len);
+	msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT);
+
+	purple_debug_error("QQ", "%s: %s\n", error, msg_utf8);
+	purple_connection_error_reason(gc, reason, msg_utf8);
+
+	g_free(error);
+	g_free(msg);
+	g_free(msg_utf8);
+	return QQ_LOGIN_REPLY_ERR;
+}
+
+void qq_request_login_2007(PurpleConnection *gc)
+{
+	qq_data *qd;
+	guint8 *buf, *raw_data;
+	gint bytes;
+	guint8 *encrypted;
+	gint encrypted_len;
+	static const guint8 login_1_16[] = {
+			0x56, 0x4E, 0xC8, 0xFB, 0x0A, 0x4F, 0xEF, 0xB3,
+			0x7A, 0x5D, 0xD8, 0x86, 0x0F, 0xAC, 0xE5, 0x1A
+	};
+	static const guint8 login_2_16[] = {
+			0x5E, 0x22, 0x3A, 0xBE, 0x13, 0xBF, 0xDA, 0x4C,
+			0xA9, 0xB7, 0x0B, 0x43, 0x63, 0x51, 0x8E, 0x28
+	};
+	static const guint8 login_3_83[] = {
+			0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00,
+			0x00, 0x00, 0x01, 0x40, 0x01, 0x01, 0x58, 0x83,
+			0xD0, 0x00, 0x10, 0x9D, 0x14, 0x64, 0x0A, 0x2E,
+			0xE2, 0x11, 0xF7, 0x90, 0xF0, 0xB5, 0x5F, 0x16,
+			0xFB, 0x41, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00,
+			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			0x00, 0x00, 0x00, 0x00, 0x02, 0x76, 0x3C, 0xEE,
+			0x4A, 0x00, 0x10, 0x86, 0x81, 0xAD, 0x1F, 0xC8,
+			0xC9, 0xCC, 0xCF, 0xCA, 0x9F, 0xFF, 0x88, 0xC0,
+			0x5C, 0x88, 0xD5
+	};
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0);
+
+	raw_data = g_newa(guint8, MAX_PACKET_SIZE - 16);
+	memset(raw_data, 0, MAX_PACKET_SIZE - 16);
+
+	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 16 bytes more */
+
+	/* Encrypted password and put in encrypted */
+	bytes = 0;
+	bytes += qq_putdata(raw_data + bytes, qd->ld.pwd_md5, sizeof(qd->ld.pwd_md5));
+	bytes += qq_put16(raw_data + bytes, 0);
+	bytes += qq_put16(raw_data + bytes, 0xffff);
+
+	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.pwd_twice_md5);
+
+	/* create packet */
+	bytes = 0;
+	bytes += qq_put16(raw_data + bytes, 0);		/* Unknow */
+	/* password encrypted */
+	bytes += qq_put16(raw_data + bytes, encrypted_len);
+	bytes += qq_putdata(raw_data + bytes, encrypted, encrypted_len);
+	/* put data which NULL string encrypted by key pwd_twice_md5 */
+	encrypted_len = qq_encrypt(encrypted, (guint8 *) "", 0, qd->ld.pwd_twice_md5);
+	g_return_if_fail(encrypted_len == 16);
+	bytes += qq_putdata(raw_data + bytes, encrypted, encrypted_len);
+	/* unknow fill 0 */
+	memset(raw_data + bytes, 0, 19);
+	bytes += 19;
+	bytes += qq_putdata(raw_data + bytes, login_1_16, sizeof(login_1_16));
+
+	bytes += qq_put8(raw_data + bytes, rand() & 0xff);
+	bytes += qq_put8(raw_data + bytes, qd->login_mode);
+	/* unknow 10 bytes zero filled*/
+	memset(raw_data + bytes, 0, 10);
+	bytes += 10;
+	/* redirect data, 15 bytes */
+	/* qq_show_packet("Redirect", qd->redirect, qd->redirect_len); */
+	bytes += qq_putdata(raw_data + bytes, qd->redirect, qd->redirect_len);
+	/* unknow fill */
+	bytes += qq_putdata(raw_data + bytes, login_2_16, sizeof(login_2_16));
+	/* captcha token get from qq_process_token_ex */
+	bytes += qq_put8(raw_data + bytes, (guint8)(qd->ld.token_ex_len & 0xff));
+	bytes += qq_putdata(raw_data + bytes, qd->ld.token_ex, qd->ld.token_ex_len);
+	/* unknow fill */
+	bytes += qq_putdata(raw_data + bytes, login_3_83, sizeof(login_3_83));
+	memset(raw_data + bytes, 0, 332 - sizeof(login_3_83));
+	bytes += 332 - sizeof(login_3_83);
+
+	/* qq_show_packet("Login", raw_data, bytes); */
+
+	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.login_key);
+
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+	memset(buf, 0, MAX_PACKET_SIZE);
+	bytes = 0;
+	/* logint token get from qq_process_check_pwd_2007 */
+	bytes += qq_put16(buf + bytes, qd->ld.login_token_len);
+	bytes += qq_putdata(buf + bytes, qd->ld.login_token, qd->ld.login_token_len);
+	bytes += qq_putdata(buf + bytes, encrypted, encrypted_len);
+
+	qd->send_seq++;
+	qq_send_cmd_encrypted(gc, QQ_CMD_LOGIN, qd->send_seq, buf, bytes, TRUE);
+}
+
+/* process the login reply packet */
+guint8 qq_process_login_2007( PurpleConnection *gc, guint8 *data, gint data_len)
+{
+	qq_data *qd;
+	gint bytes;
+	guint8 ret;
+	guint32 uid;
+	gchar *error;
+	gchar *msg;
+	gchar *msg_utf8;
+
+	g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR);
+
+	qd = (qq_data *) gc->proto_data;
+
+	bytes = 0;
+	bytes += qq_get8(&ret, data + bytes);
+	if (ret != 0) {
+		msg = g_strndup((gchar *)data + bytes, data_len - bytes);
+		msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT);
+		g_free(msg);
+
+		switch (ret) {
+			case 0x05:
+				purple_debug_error("QQ", "Server busy for %s\n", msg_utf8);
+				return QQ_LOGIN_REPLY_REDIRECT;
+			case 0x0A:
+				/* 0a 2d 9a 4b 9a 01 01 00 00 00 05 00 00 00 00 79 0e 5f fd */
+				/* Missing get server before login*/
+			default:
+				error = g_strdup_printf(
+						_("Unknow reply code when login (0x%02X):\n%s"),
+						ret, msg_utf8);
+				break;
+		}
+
+		purple_debug_error("QQ", "%s\n", error);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_OTHER_ERROR,
+				error);
+
+		qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ", data, data_len, error);
+
+		g_free(error);
+		g_free(msg_utf8);
+		return QQ_LOGIN_REPLY_ERR;
+	}
+
+	bytes += qq_getdata(qd->session_key, sizeof(qd->session_key), data + bytes);
+	purple_debug_info("QQ", "Got session_key\n");
+	get_session_md5(qd->session_md5, qd->uid, qd->session_key);
+
+	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);
+	}
+	bytes += qq_getIP(&qd->my_ip, data + bytes);
+	bytes += qq_get16(&qd->my_port, data + bytes);
+	bytes += qq_getIP(&qd->my_local_ip, data + bytes);
+	bytes += qq_get16(&qd->my_local_port, data + bytes);
+	bytes += qq_getime(&qd->login_time, data + bytes);
+	/* skip unknow 50 byte */
+	bytes += 50;
+	/* skip client key 32 byte */
+	bytes += 32;
+	/* skip unknow 12 byte */
+	bytes += 12;
+	/* last login */
+	bytes += qq_getIP(&qd->last_login_ip, data + bytes);
+	bytes += qq_getime(&qd->last_login_time[0], data + bytes);
+	purple_debug_info("QQ", "Last Login: %s, %s\n",
+			inet_ntoa(qd->last_login_ip), ctime(&qd->last_login_time[0]));
+	return QQ_LOGIN_REPLY_OK;
+}
+
+void qq_request_login_2008(PurpleConnection *gc)
+{
+	qq_data *qd;
+	guint8 *buf, *raw_data;
+	gint bytes;
+	guint8 *encrypted;
+	gint encrypted_len;
+	guint8 index, count;
+
+	static const guint8 login_1_16[] = {
+			0xD2, 0x41, 0x75, 0x12, 0xC2, 0x86, 0x57, 0x10,
+			0x78, 0x57, 0xDC, 0x24, 0x8C, 0xAA, 0x8F, 0x4E
+	};
+
+	static const guint8 login_2_16[] = {
+			0x94, 0x0B, 0x73, 0x7A, 0xA2, 0x51, 0xF0, 0x4B,
+			0x95, 0x2F, 0xC6, 0x0A, 0x5B, 0xF6, 0x76, 0x52
+	};
+	static const guint8 login_3_18[] = {
+			0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00,
+			0x00, 0x00, 0x01, 0x40, 0x01, 0x1b, 0x02, 0x84,
+			0x50, 0x00
+	};
+	static const guint8 login_4_16[] = {
+			0x2D, 0x49, 0x15, 0x55, 0x78, 0xFC, 0xF3, 0xD4,
+			0x53, 0x55, 0x60, 0x9C, 0x37, 0x9F, 0xE9, 0x59
+	};
+	static const guint8 login_5_6[] = {
+			0x02, 0x68, 0xe8, 0x07, 0x83, 0x00
+	};
+	static const guint8 login_6_16[] = {
+			0x3B, 0xCE, 0x43, 0xF1, 0x8B, 0xA4, 0x2B, 0xB5,
+			0xB3, 0x51, 0x57, 0xF7, 0x06, 0x4B, 0x18, 0xFC
+	};
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0);
+
+	raw_data = g_newa(guint8, MAX_PACKET_SIZE - 16);
+	memset(raw_data, 0, MAX_PACKET_SIZE - 16);
+
+	encrypted = g_newa(guint8, MAX_PACKET_SIZE);	/* 16 bytes more */
+
+	/* Encrypted password and put in encrypted */
+	bytes = 0;
+	bytes += qq_putdata(raw_data + bytes, qd->ld.pwd_md5, sizeof(qd->ld.pwd_md5));
+	bytes += qq_put16(raw_data + bytes, 0);
+	bytes += qq_put16(raw_data + bytes, 0xffff);
+
+	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.pwd_twice_md5);
+
+	/* create packet */
+	bytes = 0;
+	bytes += qq_put16(raw_data + bytes, 0);		/* Unknow */
+	/* password encrypted */
+	bytes += qq_put16(raw_data + bytes, encrypted_len);
+	bytes += qq_putdata(raw_data + bytes, encrypted, encrypted_len);
+	/* put data which NULL string encrypted by key pwd_twice_md5 */
+	encrypted_len = qq_encrypt(encrypted, (guint8 *) "", 0, qd->ld.pwd_twice_md5);
+	g_return_if_fail(encrypted_len == 16);
+	bytes += qq_putdata(raw_data + bytes, encrypted, encrypted_len);
+	/* unknow 19 bytes zero filled*/
+	memset(raw_data + bytes, 0, 19);
+	bytes += 19;
+	bytes += qq_putdata(raw_data + bytes, login_1_16, sizeof(login_1_16));
+
+	index = rand() % 3;	  /* can be set as 1 */
+	for( count = 0; count < encrypted_len;  count++ )
+		index ^= encrypted[count];
+	for( count = 0; count < sizeof(login_1_16);  count++ )
+		index ^= login_1_16[count];
+	bytes += qq_put8(raw_data + bytes, index);	/* random in QQ 2007*/
+
+	bytes += qq_put8(raw_data + bytes, qd->login_mode);
+	/* unknow 10 bytes zero filled*/
+	memset(raw_data + bytes, 0, 10);
+	bytes += 10;
+	/* redirect data, 15 bytes */
+	bytes += qq_putdata(raw_data + bytes, qd->redirect, qd->redirect_len);
+	/* unknow fill */
+	bytes += qq_putdata(raw_data + bytes, login_2_16, sizeof(login_2_16));
+	/* captcha token get from qq_process_token_ex */
+	bytes += qq_put8(raw_data + bytes, (guint8)(qd->ld.token_ex_len & 0xff));
+	bytes += qq_putdata(raw_data + bytes, qd->ld.token_ex, qd->ld.token_ex_len);
+	/* unknow fill */
+	bytes += qq_putdata(raw_data + bytes, login_3_18, sizeof(login_3_18));
+	bytes += qq_put8(raw_data + bytes , sizeof(login_4_16));
+	bytes += qq_putdata(raw_data + bytes, login_4_16, sizeof(login_4_16));
+	/* unknow 10 bytes zero filled*/
+	memset(raw_data + bytes, 0, 10);
+	bytes += 10;
+	/* redirect data, 15 bytes */
+	bytes += qq_putdata(raw_data + bytes, qd->redirect, qd->redirect_len);
+	/* unknow fill */
+	bytes += qq_putdata(raw_data + bytes, login_5_6, sizeof(login_5_6));
+	bytes += qq_put8(raw_data + bytes , sizeof(login_6_16));
+	bytes += qq_putdata(raw_data + bytes, login_6_16, sizeof(login_6_16));
+	/* unknow 249 bytes zero filled*/
+	memset(raw_data + bytes, 0, 249);
+	bytes += 249;
+
+	/* qq_show_packet("Login request", raw_data, bytes); */
+	encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.login_key);
+
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+	memset(buf, 0, MAX_PACKET_SIZE);
+	bytes = 0;
+	/* logint token get from qq_process_check_pwd_2007 */
+	bytes += qq_put16(buf + bytes, qd->ld.login_token_len);
+	bytes += qq_putdata(buf + bytes, qd->ld.login_token, qd->ld.login_token_len);
+	bytes += qq_putdata(buf + bytes, encrypted, encrypted_len);
+
+	qd->send_seq++;
+	qq_send_cmd_encrypted(gc, QQ_CMD_LOGIN, qd->send_seq, buf, bytes, TRUE);
+}
+
+guint8 qq_process_login_2008( PurpleConnection *gc, guint8 *data, gint data_len)
+{
+	qq_data *qd;
+	gint bytes;
+	guint8 ret;
+	guint32 uid;
+	gchar *error;
+	gchar *msg;
+	gchar *msg_utf8;
+
+	g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR);
+
+	qd = (qq_data *) gc->proto_data;
+
+	bytes = 0;
+	bytes += qq_get8(&ret, data + bytes);
+	if (ret != 0) {
+		msg = g_strndup((gchar *)data + bytes, data_len - bytes);
+		msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT);
+		g_free(msg);
+
+		switch (ret) {
+			case 0x05:
+				purple_debug_error("QQ", "Server busy for %s\n", msg_utf8);
+				return QQ_LOGIN_REPLY_REDIRECT;
+				break;
+			default:
+				error = g_strdup_printf(
+						_("Unknow reply code when login (0x%02X):\n%s"),
+						ret, msg_utf8);
+				break;
+		}
+
+		purple_debug_error("QQ", "%s\n", error);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_OTHER_ERROR,
+				error);
+
+		qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ", data, data_len, error);
+
+		g_free(error);
+		g_free(msg_utf8);
+		return QQ_LOGIN_REPLY_ERR;
+	}
+
+	bytes += qq_getdata(qd->session_key, sizeof(qd->session_key), data + bytes);
+	purple_debug_info("QQ", "Got session_key\n");
+	get_session_md5(qd->session_md5, qd->uid, qd->session_key);
+
+	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);
+	}
+	bytes += qq_getIP(&qd->my_ip, data + bytes);
+	bytes += qq_get16(&qd->my_port, data + bytes);
+	bytes += qq_getIP(&qd->my_local_ip, data + bytes);
+	bytes += qq_get16(&qd->my_local_port, data + bytes);
+	bytes += qq_getime(&qd->login_time, data + bytes);
+	/* skip 1 byte, always 0x03 */
+	/* skip 1 byte, login mode */
+	bytes = 131;
+	bytes += qq_getIP(&qd->last_login_ip, data + bytes);
+	bytes += qq_getime(&qd->last_login_time[0], data + bytes);
+	purple_debug_info("QQ", "Last Login: %s, %s\n",
+			inet_ntoa(qd->last_login_ip), ctime(&qd->last_login_time[0]));
+	return QQ_LOGIN_REPLY_OK;
+}
--- a/libpurple/protocols/qq/qq_base.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/qq_base.h	Thu Nov 06 03:20:05 2008 +0000
@@ -28,29 +28,51 @@
 #include <glib.h>
 #include "connection.h"
 
-#define QQ_TOKEN_REPLY_OK 	0x00
-
 #define QQ_LOGIN_REPLY_OK							0x00
 #define QQ_LOGIN_REPLY_REDIRECT				0x01
-#define QQ_LOGIN_REPLY_ERR_PWD					0x05
-#define QQ_LOGIN_REPLY_NEED_REACTIVE		0x06
-#define QQ_LOGIN_REPLY_REDIRECT_EX			0x0A
-#define QQ_LOGIN_REPLY_ERR_MISC				0xff	/* defined by myself */
+/* defined by myself */
+#define QQ_LOGIN_REPLY_CAPTCHA_DLG			0xfd
+#define QQ_LOGIN_REPLY_NEXT_TOKEN_EX		0xfe
+#define QQ_LOGIN_REPLY_ERR							0xff
 
-#define QQ_LOGIN_MODE_NORMAL        0x0a
-#define QQ_LOGIN_MODE_AWAY	    0x1e
-#define QQ_LOGIN_MODE_HIDDEN        0x28
+#define QQ_LOGIN_MODE_NORMAL		0x0a
+#define QQ_LOGIN_MODE_AWAY	    	0x1e
+#define QQ_LOGIN_MODE_HIDDEN		0x28
 
 #define QQ_UPDATE_ONLINE_INTERVAL   300	/* in sec */
 
-void qq_send_packet_token(PurpleConnection *gc);
-guint8 qq_process_token_reply(PurpleConnection *gc, guint8 *buf, gint buf_len);
+void qq_request_token(PurpleConnection *gc);
+guint8 qq_process_token(PurpleConnection *gc, guint8 *buf, gint buf_len);
+
+void qq_request_login(PurpleConnection *gc);
+guint8 qq_process_login( PurpleConnection *gc, guint8 *data, gint data_len);
+
+void qq_request_logout(PurpleConnection *gc);
 
-void qq_send_packet_login(PurpleConnection *gc);
-guint8 qq_process_login_reply( PurpleConnection *gc, guint8 *data, gint data_len);
+void qq_request_keep_alive(PurpleConnection *gc);
+gboolean qq_process_keep_alive(guint8 *data, gint data_len, PurpleConnection *gc);
+
+void qq_request_keep_alive_2007(PurpleConnection *gc);
+gboolean qq_process_keep_alive_2007(guint8 *data, gint data_len, PurpleConnection *gc);
+
+void qq_request_keep_alive_2008(PurpleConnection *gc);
+gboolean qq_process_keep_alive_2008(guint8 *data, gint data_len, PurpleConnection *gc);
 
-void qq_send_packet_logout(PurpleConnection *gc);
+/* for QQ2007/2008 */
+void qq_request_get_server(PurpleConnection *gc);
+guint16 qq_process_get_server(PurpleConnection *gc, guint8 *rcved, gint rcved_len);
+
+void qq_request_token_ex(PurpleConnection *gc);
+void qq_request_token_ex_next(PurpleConnection *gc);
+guint8 qq_process_token_ex(PurpleConnection *gc, guint8 *buf, gint buf_len);
+void qq_captcha_input_dialog(PurpleConnection *gc,qq_captcha_data *captcha);
 
-void qq_send_packet_keep_alive(PurpleConnection *gc);
-gboolean qq_process_keep_alive(guint8 *data, gint data_len, PurpleConnection *gc);
+void qq_request_check_pwd(PurpleConnection *gc);
+guint8 qq_process_check_pwd( PurpleConnection *gc, guint8 *data, gint data_len);
+
+void qq_request_login_2007(PurpleConnection *gc);
+guint8 qq_process_login_2007( PurpleConnection *gc, guint8 *data, gint data_len);
+
+void qq_request_login_2008(PurpleConnection *gc);
+guint8 qq_process_login_2008( PurpleConnection *gc, guint8 *data, gint data_len);
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/qq/qq_define.c	Thu Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,266 @@
+/**
+ * @file qq_define.c
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include "internal.h"
+
+#include "qq_define.h"
+
+#define QQ_CLIENT_062E 0x062e	/* GB QQ2000c build 0630 */
+#define QQ_CLIENT_072E 0x072e	/* EN QQ2000c build 0305 */
+#define QQ_CLIENT_0801 0x0801	/* EN QQ2000c build 0630 */
+#define QQ_CLIENT_0A1D 0x0a1d	/* GB QQ2003c build 0808 */
+#define QQ_CLIENT_0B07 0x0b07	/* GB QQ2003c build 0925 */
+#define QQ_CLIENT_0B2F 0x0b2f	/* GB QQ2003iii build 0117 */
+#define QQ_CLIENT_0B35 0x0b35	/* GB QQ2003iii build 0304 (offical release) */
+#define QQ_CLIENT_0B37 0x0b37	/* GB QQ2003iii build 0304 (April 05 updates) */
+#define QQ_CLIENT_0E1B 0x0e1b	/* QQ2005 ? */
+#define QQ_CLIENT_0E35 0x0e35	/* EN QQ2005 V05.0.200.020 */
+#define QQ_CLIENT_0F15 0x0f15	/* QQ2006 Spring Festival build */
+#define QQ_CLIENT_0F5F 0x0f5f	/* QQ2006 final build */
+
+#define QQ_CLIENT_0C0B 0x0C0B	/* QQ2004 */
+#define QQ_CLIENT_0C0D 0x0C0D	/* QQ2004 preview*/
+#define QQ_CLIENT_0C21 0x0C21	/* QQ2004 */
+#define QQ_CLIENT_0C49 0x0C49	/* QQ2004II */
+#define QQ_CLIENT_0D05 0x0D05	/* QQ2005 beta1 */
+#define QQ_CLIENT_0D51 0x0D51	/* QQ2005 beta2 */
+#define QQ_CLIENT_0D61 0x0D61	/* QQ2005 */
+#define QQ_CLIENT_05A5 0x05A5	/* ? */
+#define QQ_CLIENT_05F1 0x0F15	/* QQ2006 Spring Festival */
+#define QQ_CLIENT_0F4B 0x0F4B	/* QQ2006 Beta 3  */
+
+#define QQ_CLIENT_1105 0x1105	/* QQ2007 beta4*/
+#define QQ_CLIENT_1203 0x1203	/* QQ2008 */
+#define QQ_CLIENT_1205 0x1205	/* QQ2008 Qi Fu */
+#define QQ_CLIENT_120B 0x120B	/* QQ2008 July 8.0.978.400 */
+#define QQ_CLIENT_1412 0x1412	/* QQMac 1.0 preview1 build 670 */
+#define QQ_CLIENT_1441 0x1441	/* QQ2009 preview2 */
+
+#define QQ_SERVER_0100 0x0100	/* server */
+
+
+/* given source tag, return its description accordingly */
+const gchar *qq_get_ver_desc(gint source)
+{
+	switch (source) {
+	case QQ_CLIENT_062E:
+		return "GB QQ2000c build 0630";
+	case QQ_CLIENT_072E:
+		return "En QQ2000c build 0305";
+	case QQ_CLIENT_0801:
+		return "En QQ2000c build 0630";
+	case QQ_CLIENT_0A1D:
+		return "GB QQ2003ii build 0808";
+	case QQ_CLIENT_0B07:
+		return "GB QQ2003ii build 0925";
+	case QQ_CLIENT_0B2F:
+		return "GB QQ2003iii build 0117";
+	case QQ_CLIENT_0B35:
+		return "GB QQ2003iii build 0304";
+	case QQ_CLIENT_0B37:
+		return "GB QQ2003iii build 0304 (April 5 update)";
+	case QQ_CLIENT_0C0B:
+		return "QQ2004";
+	case QQ_CLIENT_0C0D:
+		return "QQ2004 preview";
+	case QQ_CLIENT_0C21:
+		return "QQ2004";
+	case QQ_CLIENT_0C49:
+		return "QQ2004II";
+	case QQ_CLIENT_0D05:
+		return "QQ2005 beta1";
+	case QQ_CLIENT_0D51:
+		return "QQ2005 beta2";
+	case QQ_CLIENT_0D55:
+	case QQ_CLIENT_0D61:
+		return "QQ2005";
+	case QQ_CLIENT_0E1B:
+		return "QQ2005 or QQ2006";
+	case QQ_CLIENT_0E35:
+		return "En QQ2005 V05.0.200.020";
+	case QQ_CLIENT_0F15:
+		return "QQ2006 Spring Festival";
+	case QQ_CLIENT_0F4B:
+		return "QQ2006 beta3";
+	case QQ_CLIENT_0F5F:
+		return "QQ2006 final build";
+	case QQ_CLIENT_1105:
+		return "QQ2007 beta4";
+	case QQ_CLIENT_111D:
+		return "QQ2007";
+	case QQ_CLIENT_115B:
+	case QQ_CLIENT_1203:
+	case QQ_CLIENT_1205:
+	case QQ_CLIENT_120B:
+		return "QQ2008";
+	case QQ_CLIENT_1412:
+		return "QQMac 1.0 preview1 build 670";
+	case QQ_CLIENT_1441:
+		return "QQ2009 preview2";
+	case QQ_SERVER_0100:
+		return "QQ Server 0100";
+	default:
+		return "Unknown Version";
+	}
+}
+
+/* given command alias, return the command name accordingly */
+const gchar *qq_get_cmd_desc(gint cmd)
+{
+	switch (cmd) {
+	case QQ_CMD_LOGOUT:
+		return "QQ_CMD_LOGOUT";
+	case QQ_CMD_KEEP_ALIVE:
+		return "QQ_CMD_KEEP_ALIVE";
+	case QQ_CMD_UPDATE_INFO:
+		return "QQ_CMD_UPDATE_INFO";
+	case QQ_CMD_SEARCH_USER:
+		return "QQ_CMD_SEARCH_USER";
+	case QQ_CMD_GET_BUDDY_INFO:
+		return "QQ_CMD_GET_BUDDY_INFO";
+	case QQ_CMD_ADD_BUDDY_NO_AUTH:
+		return "QQ_CMD_ADD_BUDDY_NO_AUTH";
+	case QQ_CMD_REMOVE_BUDDY:
+		return "QQ_CMD_REMOVE_BUDDY";
+	case QQ_CMD_ADD_BUDDY_AUTH:
+		return "QQ_CMD_ADD_BUDDY_AUTH";
+	case QQ_CMD_CHANGE_STATUS:
+		return "QQ_CMD_CHANGE_STATUS";
+	case QQ_CMD_ACK_SYS_MSG:
+		return "QQ_CMD_ACK_SYS_MSG";
+	case QQ_CMD_SEND_IM:
+		return "QQ_CMD_SEND_IM";
+	case QQ_CMD_RECV_IM:
+		return "QQ_CMD_RECV_IM";
+	case QQ_CMD_REMOVE_ME:
+		return "QQ_CMD_REMOVE_ME";
+	case QQ_CMD_LOGIN:
+		return "QQ_CMD_LOGIN";
+	case QQ_CMD_GET_BUDDIES_LIST:
+		return "QQ_CMD_GET_BUDDIES_LIST";
+	case QQ_CMD_GET_BUDDIES_ONLINE:
+		return "QQ_CMD_GET_BUDDIES_ONLINE";
+	case QQ_CMD_ROOM:
+		return "QQ_CMD_ROOM";
+	case QQ_CMD_GET_BUDDIES_AND_ROOMS:
+		return "QQ_CMD_GET_BUDDIES_AND_ROOMS";
+	case QQ_CMD_GET_LEVEL:
+		return "QQ_CMD_GET_LEVEL";
+	case QQ_CMD_TOKEN:
+		return "QQ_CMD_TOKEN";
+	case QQ_CMD_RECV_MSG_SYS:
+		return "QQ_CMD_RECV_MSG_SYS";
+	case QQ_CMD_BUDDY_CHANGE_STATUS:
+		return "QQ_CMD_BUDDY_CHANGE_STATUS";
+	case QQ_CMD_GET_SERVER:
+		return "QQ_CMD_GET_SERVER";
+	case QQ_CMD_TOKEN_EX:
+		return "QQ_CMD_TOKEN_EX";
+	case QQ_CMD_CHECK_PWD:
+		return "QQ_CMD_CHECK_PWD";
+	case QQ_CMD_AUTH_CODE:
+		return "QQ_CMD_AUTH_CODE";
+	case QQ_CMD_ADD_BUDDY_NO_AUTH_EX:
+		return "QQ_CMD_ADD_BUDDY_NO_AUTH_EX";
+	case QQ_CMD_ADD_BUDDY_AUTH_EX:
+		return "QQ_CMD_BUDDY_ADD_AUTH_EX";
+	case QQ_CMD_BUDDY_CHECK_CODE:
+		return "QQ_CMD_BUDDY_CHECK_CODE";
+	case QQ_CMD_BUDDY_QUESTION:
+		return "QQ_CMD_BUDDY_QUESTION";
+	default:
+		return "Unknown CMD";
+	}
+}
+
+const gchar *qq_get_room_cmd_desc(gint room_cmd)
+{
+	switch (room_cmd) {
+	case QQ_ROOM_CMD_CREATE:
+		return "QQ_ROOM_CMD_CREATE";
+	case QQ_ROOM_CMD_MEMBER_OPT:
+		return "QQ_ROOM_CMD_MEMBER_OPT";
+	case QQ_ROOM_CMD_CHANGE_INFO:
+		return "QQ_ROOM_CMD_CHANGE_INFO";
+	case QQ_ROOM_CMD_GET_INFO:
+		return "QQ_ROOM_CMD_GET_INFO";
+	case QQ_ROOM_CMD_ACTIVATE:
+		return "QQ_ROOM_CMD_ACTIVATE";
+	case QQ_ROOM_CMD_SEARCH:
+		return "QQ_ROOM_CMD_SEARCH";
+	case QQ_ROOM_CMD_JOIN:
+		return "QQ_ROOM_CMD_JOIN";
+	case QQ_ROOM_CMD_AUTH:
+		return "QQ_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";
+	case QQ_ROOM_CMD_GET_ONLINES:
+		return "QQ_ROOM_CMD_GET_ONLINES";
+	case QQ_ROOM_CMD_GET_BUDDIES:
+		return "QQ_ROOM_CMD_GET_BUDDIES";
+	case QQ_ROOM_CMD_CHANGE_CARD:
+		return "QQ_ROOM_CMD_CHANGE_CARD";
+	case QQ_ROOM_CMD_GET_REALNAMES:
+		return "QQ_ROOM_CMD_GET_REALNAMES";
+	case QQ_ROOM_CMD_GET_CARD:
+		return "QQ_ROOM_CMD_GET_CARD";
+	case QQ_ROOM_CMD_SEND_IM_EX:
+		return "QQ_ROOM_CMD_SEND_IM_EX";
+	case QQ_ROOM_CMD_ADMIN:
+		return "QQ_ROOM_CMD_ADMIN";
+	case QQ_ROOM_CMD_TRANSFER:
+		return "QQ_ROOM_CMD_TRANSFER";
+	case QQ_ROOM_CMD_TEMP_CREATE:
+		return "QQ_ROOM_CMD_TEMP_CREATE";
+	case QQ_ROOM_CMD_TEMP_CHANGE_MEMBER:
+		return "QQ_ROOM_CMD_TEMP_CHANGE_MEMBER";
+	case QQ_ROOM_CMD_TEMP_QUIT:
+		return "QQ_ROOM_CMD_TEMP_QUIT";
+	case QQ_ROOM_CMD_TEMP_GET_INFO:
+		return "QQ_ROOM_CMD_TEMP_GET_INFO";
+	case QQ_ROOM_CMD_TEMP_SEND_IM:
+		return "QQ_ROOM_CMD_TEMP_SEND_IM";
+	case QQ_ROOM_CMD_TEMP_GET_MEMBERS:
+		return "QQ_ROOM_CMD_TEMP_GET_MEMBERS";
+	default:
+		return "Unknown Room Command";
+	}
+}
+
+/* check if status means online or offline */
+gboolean is_online(guint8 status)
+{
+	switch(status) {
+		case QQ_BUDDY_ONLINE_NORMAL:
+		case QQ_BUDDY_ONLINE_AWAY:
+		case QQ_BUDDY_ONLINE_INVISIBLE:
+		case QQ_BUDDY_ONLINE_BUSY:
+			return TRUE;
+		case QQ_BUDDY_CHANGE_TO_OFFLINE:
+			return FALSE;
+	}
+	return FALSE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/qq/qq_define.h	Thu Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,136 @@
+/**
+ * @file qq_define.h
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#ifndef _QQ_HEADER_INFO_H_
+#define _QQ_HEADER_INFO_H_
+
+#include <glib.h>
+
+#define QQ_UDP_HEADER_LENGTH    7
+#define QQ_TCP_HEADER_LENGTH    9
+
+#define QQ_PACKET_TAG			0x02	/* all QQ text packets starts with it */
+#define QQ_PACKET_TAIL			0x03	/* all QQ text packets end with it */
+
+#define QQ_CLIENT_0D55 0x0d55	/* QQ2005 used by openq before */
+#define QQ_CLIENT_111D 0x111D	/* QQ2007 */
+#define QQ_CLIENT_115B 0x115B	/* QQ2008 He Sui*/
+
+const gchar *qq_get_ver_desc(gint source);
+
+/* list of known QQ commands */
+enum {
+	QQ_CMD_LOGOUT = 0x0001,				/* log out */
+	QQ_CMD_KEEP_ALIVE = 0x0002,			/* get onlines from tencent */
+	QQ_CMD_UPDATE_INFO = 0x0004,			/* update information */
+	QQ_CMD_SEARCH_USER = 0x0005,			/* search for user */
+	QQ_CMD_GET_BUDDY_INFO = 0x0006,			/* get user information */
+	QQ_CMD_ADD_BUDDY_NO_AUTH = 0x0009,		/* add buddy without auth */
+	QQ_CMD_REMOVE_BUDDY = 0x000a,			/* delete a buddy  */
+	QQ_CMD_ADD_BUDDY_AUTH = 0x000b,			/* buddy authentication */
+	QQ_CMD_CHANGE_STATUS = 0x000d,		/* change my online status */
+	QQ_CMD_ACK_SYS_MSG = 0x0012,			/* ack system message */
+	QQ_CMD_SEND_IM = 0x0016,			/* send message */
+	QQ_CMD_RECV_IM = 0x0017,			/* receive message */
+	QQ_CMD_REMOVE_ME = 0x001c,			/* remove self */
+	QQ_CMD_REQUEST_KEY = 0x001d,			/* request key for file transfer */
+	QQ_CMD_CELL_PHONE_1 = 0x0021,			/* cell phone 1 */
+	QQ_CMD_LOGIN = 0x0022,				/* login */
+	QQ_CMD_GET_BUDDIES_LIST = 0x0026,		/* get buddies list */
+	QQ_CMD_GET_BUDDIES_ONLINE = 0x0027,		/* get online buddies list */
+	QQ_CMD_CELL_PHONE_2 = 0x0029,			/* cell phone 2 */
+	QQ_CMD_ROOM = 0x0030,			/* room command */
+	QQ_CMD_GET_BUDDIES_AND_ROOMS = 0x0058,
+	QQ_CMD_GET_LEVEL = 0x005C,			/* get level for one or more buddies */
+	QQ_CMD_TOKEN  = 0x0062, 		/* get login token */
+	QQ_CMD_RECV_MSG_SYS = 0x0080,			/* receive a system message */
+	QQ_CMD_BUDDY_CHANGE_STATUS = 0x0081,	/* buddy change status */
+	/* for QQ2007*/
+	QQ_CMD_GET_SERVER = 0x0091,					/* select login server */
+	QQ_CMD_TOKEN_EX = 0x00BA,						/* get LOGIN token */
+	QQ_CMD_CHECK_PWD = 0x00DD,				/* Password verify */
+	QQ_CMD_AUTH_CODE = 0x00AE,				/* the request verification of information */
+	QQ_CMD_ADD_BUDDY_NO_AUTH_EX = 0x00A7,			/* add friend without auth */
+	QQ_CMD_ADD_BUDDY_AUTH_EX = 0x00A8, 				/* add buddy with auth */
+	QQ_CMD_BUDDY_CHECK_CODE =  0x00B5,
+	QQ_CMD_BUDDY_QUESTION =  0x00B7,
+};
+
+const gchar *qq_get_cmd_desc(gint type);
+
+enum {
+	QQ_ROOM_CMD_CREATE = 0x01,
+	QQ_ROOM_CMD_MEMBER_OPT = 0x02,
+	QQ_ROOM_CMD_CHANGE_INFO = 0x03,
+	QQ_ROOM_CMD_GET_INFO = 0x04,
+	QQ_ROOM_CMD_ACTIVATE = 0x05,
+	QQ_ROOM_CMD_SEARCH = 0x06,
+	QQ_ROOM_CMD_JOIN = 0x07,
+	QQ_ROOM_CMD_AUTH = 0x08,
+	QQ_ROOM_CMD_QUIT = 0x09,
+	QQ_ROOM_CMD_SEND_MSG = 0x0a,
+	QQ_ROOM_CMD_GET_ONLINES = 0x0b,
+	QQ_ROOM_CMD_GET_BUDDIES = 0x0c,
+
+	QQ_ROOM_CMD_CHANGE_CARD = 0x0E,
+	QQ_ROOM_CMD_GET_REALNAMES = 0x0F,
+	QQ_ROOM_CMD_GET_CARD = 0x10,
+	QQ_ROOM_CMD_SEND_IM_EX = 0x1A,
+	QQ_ROOM_CMD_ADMIN = 0x1B,
+	QQ_ROOM_CMD_TRANSFER = 0x1C,
+	QQ_ROOM_CMD_TEMP_CREATE = 0x30,
+	QQ_ROOM_CMD_TEMP_CHANGE_MEMBER = 0x31,
+	QQ_ROOM_CMD_TEMP_QUIT = 0x32,
+	QQ_ROOM_CMD_TEMP_GET_INFO = 0x33,
+	QQ_ROOM_CMD_TEMP_SEND_IM = 0x35,
+	QQ_ROOM_CMD_TEMP_GET_MEMBERS = 0x37,
+};
+
+const gchar *qq_get_room_cmd_desc(gint room_cmd);
+
+enum {
+	QQ_SERVER_BUDDY_ADDED = 1,
+	QQ_SERVER_BUDDY_ADD_REQUEST = 2,
+	QQ_SERVER_BUDDY_ADDED_ME = 3,
+	QQ_SERVER_BUDDY_REJECTED_ME = 4,
+	QQ_SERVER_NOTICE= 6,
+	QQ_SERVER_NEW_CLIENT = 9,
+	QQ_SERVER_BUDDY_ADDING_EX = 40,
+	QQ_SERVER_BUDDY_ADD_REQUEST_EX = 41,
+	QQ_SERVER_BUDDY_ADDED_ANSWER = 42,
+	QQ_SERVER_BUDDY_ADDED_EX = 43,
+};
+
+enum {
+	QQ_BUDDY_OFFLINE = 0x00,
+	QQ_BUDDY_ONLINE_NORMAL = 10,
+	QQ_BUDDY_CHANGE_TO_OFFLINE = 20,
+	QQ_BUDDY_ONLINE_AWAY = 30,
+	QQ_BUDDY_ONLINE_INVISIBLE = 40,
+	QQ_BUDDY_ONLINE_BUSY = 50,
+};
+
+gboolean is_online(guint8 status);
+
+#endif
--- a/libpurple/protocols/qq/qq_network.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/qq_network.c	Thu Nov 06 03:20:05 2008 +0000
@@ -28,9 +28,9 @@
 
 #include "buddy_info.h"
 #include "group_info.h"
-#include "group_free.h"
+#include "group_internal.h"
 #include "qq_crypt.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "qq_base.h"
 #include "buddy_list.h"
 #include "packet_parse.h"
@@ -160,7 +160,7 @@
 		qd->connect_watcher = 0;
 	}
 
-	if (qd->fd >= 0 && qd->token != NULL && qd->token_len >= 0) {
+	if (qd->fd >= 0 && qd->ld.token != NULL && qd->ld.token_len > 0) {
 		purple_debug_info("QQ", "Connect ok\n");
 		return FALSE;
 	}
@@ -202,7 +202,8 @@
 
 	if (qd->curr_server == NULL || strlen (qd->curr_server) == 0 || qd->connect_retry <= 0) {
 		if ( set_new_server(qd) != TRUE) {
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+			purple_connection_error_reason(gc,
+					PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 					_("Failed to connect all servers"));
 			return FALSE;
 		}
@@ -220,7 +221,8 @@
 
 	qd->connect_retry--;
 	if ( !connect_to_server(gc, server, port) ) {
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+			purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 				_("Unable to connect."));
 	}
 
@@ -228,6 +230,19 @@
 	return FALSE;	/* timeout callback stops */
 }
 
+static void redirect_server(PurpleConnection *gc)
+{
+	qq_data *qd;
+	qd = (qq_data *) gc->proto_data;
+
+	if (qd->check_watcher > 0) {
+			purple_timeout_remove(qd->check_watcher);
+			qd->check_watcher = 0;
+	}
+	if (qd->connect_watcher > 0)	purple_timeout_remove(qd->connect_watcher);
+	qd->connect_watcher = purple_timeout_add_seconds(QQ_CONNECT_INTERVAL, qq_connect_later, gc);
+}
+
 /* process the incoming packet from qq_pending */
 static gboolean packet_process(PurpleConnection *gc, guint8 *buf, gint buf_len)
 {
@@ -242,6 +257,7 @@
 	guint32 room_id;
 	gint update_class;
 	guint32 ship32;
+	int ret;
 
 	qq_transaction *trans;
 
@@ -285,23 +301,22 @@
 
 	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);
+	}
 
 	switch (cmd) {
 		case QQ_CMD_TOKEN:
-			if (qq_process_token_reply(gc, buf + bytes, bytes_not_read) == QQ_TOKEN_REPLY_OK) {
-				qq_send_packet_login(gc);
-			}
-			break;
+		case QQ_CMD_GET_SERVER:
+		case QQ_CMD_TOKEN_EX:
+		case QQ_CMD_CHECK_PWD:
 		case QQ_CMD_LOGIN:
-			qq_proc_login_cmd(gc, buf + bytes, bytes_not_read);
-			/* check is redirect or not, and do it now */
-			if (qd->redirect_ip.s_addr != 0) {
-				if (qd->check_watcher > 0) {
-					purple_timeout_remove(qd->check_watcher);
-					qd->check_watcher = 0;
+			ret = qq_proc_login_cmds(gc, cmd, seq, buf + bytes, bytes_not_read, update_class, ship32);
+			if (ret != QQ_LOGIN_REPLY_OK) {
+				if (ret == QQ_LOGIN_REPLY_REDIRECT) {
+					redirect_server(gc);
 				}
-				if (qd->connect_watcher > 0)	purple_timeout_remove(qd->connect_watcher);
-				qd->connect_watcher = purple_timeout_add_seconds(QQ_CONNECT_INTERVAL, qq_connect_later, gc);
 				return FALSE;	/* do nothing after this function and return now */
 			}
 			break;
@@ -312,10 +327,10 @@
 			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_cmd(gc, seq, room_cmd, room_id, buf + bytes, bytes_not_read, update_class, ship32);
+			qq_proc_room_cmds(gc, seq, room_cmd, room_id, buf + bytes, bytes_not_read, update_class, ship32);
 			break;
 		default:
-			qq_proc_client_cmd(gc, cmd, seq, buf + bytes, bytes_not_read, update_class, ship32);
+			qq_proc_client_cmds(gc, cmd, seq, buf + bytes, bytes_not_read, update_class, ship32);
 			break;
 	}
 
@@ -342,7 +357,8 @@
 	qd = (qq_data *) gc->proto_data;
 
 	if(cond != PURPLE_INPUT_READ) {
-		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 				_("Socket error"));
 		return;
 	}
@@ -366,7 +382,9 @@
 			return;
 
 		error_msg = g_strdup_printf(_("Lost connection with server:\n%d, %s"), errno, g_strerror(errno));
-		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_msg);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				error_msg);
 		g_free(error_msg);
 		return;
 	} else if (buf_len == 0) {
@@ -468,7 +486,8 @@
 	qd = (qq_data *) gc->proto_data;
 
 	if(cond != PURPLE_INPUT_READ) {
-		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 				_("Socket error"));
 		return;
 	}
@@ -478,7 +497,8 @@
 	/* here we have UDP proxy suppport */
 	buf_len = read(source, buf, MAX_PACKET_SIZE);
 	if (buf_len <= 0) {
-		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 				_("Unable to read from socket"));
 		return;
 	}
@@ -526,7 +546,9 @@
 	if (ret < 0) {
 		/* TODO: what to do here - do we really have to disconnect? */
 		purple_debug_error("UDP_SEND_OUT", "Send failed: %d, %s\n", errno, g_strerror(errno));
-		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, g_strerror(errno));
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				g_strerror(errno));
 	}
 	return ret;
 }
@@ -558,8 +580,9 @@
 		return;
 	else if (ret < 0) {
 		/* TODO: what to do here - do we really have to disconnect? */
-		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-		                               _("Write Error"));
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+		        _("Write Error"));
 		return;
 	}
 
@@ -603,7 +626,9 @@
 		/* TODO: what to do here - do we really have to disconnect? */
 		purple_debug_error("TCP_SEND_OUT",
 			"Send to socket %d failed: %d, %s\n", qd->fd, errno, g_strerror(errno));
-		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, g_strerror(errno));
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				g_strerror(errno));
 		return ret;
 	}
 
@@ -630,7 +655,8 @@
 	is_lost_conn = qq_trans_scan(gc);
 	if (is_lost_conn) {
 		purple_connection_error_reason(gc,
-			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Connection lost"));
+			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+			_("Connection lost"));
 		return TRUE;
 	}
 
@@ -641,7 +667,13 @@
 	qd->itv_count.keep_alive--;
 	if (qd->itv_count.keep_alive <= 0) {
 		qd->itv_count.keep_alive = qd->itv_config.keep_alive;
-		qq_send_packet_keep_alive(gc);
+		if (qd->client_version >= 2008) {
+			qq_request_keep_alive_2008(gc);
+		} else if (qd->client_version >= 2007) {
+			qq_request_keep_alive_2007(gc);
+		} else {
+			qq_request_keep_alive(gc);
+		}
 		return TRUE;
 	}
 
@@ -659,12 +691,15 @@
 	return TRUE;		/* if return FALSE, timeout callback stops */
 }
 
-static void do_request_token(PurpleConnection *gc)
+static void set_all_keys(PurpleConnection *gc)
 {
 	qq_data *qd;
-	gchar *conn_msg;
 	const gchar *passwd;
-
+	guint8 *dest;
+	int dest_len = QQ_KEY_LENGTH;
+#ifndef DEBUG
+	int bytes;
+#endif
 	/* _qq_show_socket("Got login socket", source); */
 
 	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
@@ -675,27 +710,25 @@
 	qd->send_seq = rand() & 0xffff;
 
 	qd->is_login = FALSE;
-	qd->channel = 1;
 	qd->uid = strtol(purple_account_get_username(purple_connection_get_account(gc)), NULL, 10);
 
+#ifdef DEBUG
+	memset(qd->ld.random_key, 0x01, sizeof(qd->ld.random_key));
+#else
+	for (bytes = 0; bytes < sizeof(qd->ld.random_key); bytes++)	{
+		qd->ld.random_key[bytes] = (guint8) (rand() & 0xff);
+	}
+#endif
+
 	/* now generate md5 processed passwd */
 	passwd = purple_account_get_password(purple_connection_get_account(gc));
 
 	/* use twice-md5 of user password as session key since QQ 2003iii */
-	qq_get_md5(qd->password_twice_md5, sizeof(qd->password_twice_md5),
-		(guint8 *)passwd, strlen(passwd));
-	qq_get_md5(qd->password_twice_md5, sizeof(qd->password_twice_md5),
-		qd->password_twice_md5, sizeof(qd->password_twice_md5));
+	dest = qd->ld.pwd_md5;
+	qq_get_md5(dest, dest_len, (guint8 *)passwd, strlen(passwd));
 
-	g_return_if_fail(qd->network_watcher == 0);
-	qd->network_watcher = purple_timeout_add_seconds(qd->itv_config.resend, network_timeout, gc);
-
-	/* Update the login progress status display */
-	conn_msg = g_strdup_printf(_("Request token"));
-	purple_connection_update_progress(gc, conn_msg, 2, QQ_CONNECT_STEPS);
-	g_free(conn_msg);
-
-	qq_send_packet_token(gc);
+	dest = qd->ld.pwd_twice_md5;
+	qq_get_md5(dest, dest_len, qd->ld.pwd_md5, dest_len);
 }
 
 /* the callback function after socket is built
@@ -740,7 +773,19 @@
 		conn->input_handler = purple_input_add(source, PURPLE_INPUT_READ, udp_pending, gc);
 	}
 
-	do_request_token( gc );
+	g_return_if_fail(qd->network_watcher == 0);
+	qd->network_watcher = purple_timeout_add_seconds(qd->itv_config.resend, network_timeout, gc);
+
+	set_all_keys( gc );
+
+	if (qd->client_version >= 2007) {
+		purple_connection_update_progress(gc, _("Get server ..."), 2, QQ_CONNECT_STEPS);
+		qq_request_get_server(gc);
+		return;
+	}
+
+	purple_connection_update_progress(gc, _("Request token"), 2, QQ_CONNECT_STEPS);
+	qq_request_token(gc);
 }
 
 #ifndef purple_proxy_connect_udp
@@ -811,8 +856,8 @@
 
 	if (!hosts || !hosts->data) {
 		purple_connection_error_reason(gc,
-			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-			_("Couldn't resolve host"));
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Couldn't resolve host"));
 		return;
 	}
 
@@ -884,21 +929,19 @@
 {
 	PurpleAccount *account ;
 	qq_data *qd;
-	gchar *conn_msg;
 
 	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, FALSE);
 	account = purple_connection_get_account(gc);
 	qd = (qq_data *) gc->proto_data;
 
 	if (server == NULL || strlen(server) == 0 || port == 0) {
-		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 				_("Invalid server or port"));
 		return FALSE;
 	}
 
-	conn_msg = g_strdup_printf( _("Connecting server %s, retries %d"), server, port);
-	purple_connection_update_progress(gc, conn_msg, 1, QQ_CONNECT_STEPS);
-	g_free(conn_msg);
+	purple_connection_update_progress(gc, _("Connecting server ..."), 1, QQ_CONNECT_STEPS);
 
 	purple_debug_info("QQ", "Connect to %s:%d\n", server, port);
 
@@ -959,7 +1002,7 @@
 
 	/* finish  all I/O */
 	if (qd->fd >= 0 && qd->is_login) {
-		qq_send_packet_logout(gc);
+		qq_request_logout(gc);
 	}
 
 	/* not connected */
@@ -984,23 +1027,20 @@
 
 	qq_trans_remove_all(gc);
 
-	if (qd->token) {
-		purple_debug_info("QQ", "free token\n");
-		g_free(qd->token);
-		qd->token = NULL;
-		qd->token_len = 0;
-	}
-	memset(qd->inikey, 0, sizeof(qd->inikey));
-	memset(qd->password_twice_md5, 0, sizeof(qd->password_twice_md5));
+	memset(qd->ld.random_key, 0, sizeof(qd->ld.random_key));
+	memset(qd->ld.pwd_md5, 0, sizeof(qd->ld.pwd_md5));
+	memset(qd->ld.pwd_twice_md5, 0, sizeof(qd->ld.pwd_twice_md5));
+	memset(qd->ld.login_key, 0, sizeof(qd->ld.login_key));
 	memset(qd->session_key, 0, sizeof(qd->session_key));
 	memset(qd->session_md5, 0, sizeof(qd->session_md5));
 
+	qd->my_local_ip.s_addr = 0;
+	qd->my_local_port = 0;
 	qd->my_ip.s_addr = 0;
+	qd->my_port = 0;
 
-	qq_group_free_all(qd);
-	qq_add_buddy_request_free(qd);
-	qq_info_query_free(qd);
-	qq_buddies_list_free(gc->account, qd);
+	qq_room_data_free_all(gc);
+	qq_buddy_data_free_all(gc);
 }
 
 static gint packet_encap(qq_data *qd, guint8 *buf, gint maxlen, guint16 cmd, guint16 seq,
@@ -1017,7 +1057,7 @@
 	}
 	/* now comes the normal QQ packet as UDP */
 	bytes += qq_put8(buf + bytes, QQ_PACKET_TAG);
-	bytes += qq_put16(buf + bytes, QQ_CLIENT);
+	bytes += qq_put16(buf + bytes, qd->client_tag);
 	bytes += qq_put16(buf + bytes, cmd);
 
 	bytes += qq_put16(buf + bytes, seq);
@@ -1064,7 +1104,7 @@
 }
 
 gint qq_send_cmd_encrypted(PurpleConnection *gc, guint16 cmd, guint16 seq,
-	guint8 *encrypted_data, gint encrypted_len, gboolean is_save2trans)
+	guint8 *encrypted, gint encrypted_len, gboolean is_save2trans)
 {
 	gint sent_len;
 
@@ -1074,9 +1114,9 @@
 				seq, qq_get_cmd_desc(cmd), cmd, encrypted_len);
 #endif
 
-	sent_len = packet_send_out(gc, cmd, seq, encrypted_data, encrypted_len);
+	sent_len = packet_send_out(gc, cmd, seq, encrypted, encrypted_len);
 	if (is_save2trans)  {
-		qq_trans_add_client_cmd(gc, cmd, seq, encrypted_data, encrypted_len, 0, 0);
+		qq_trans_add_client_cmd(gc, cmd, seq, encrypted, encrypted_len, 0, 0);
 	}
 	return sent_len;
 }
@@ -1086,7 +1126,7 @@
 	guint8 *data, gint data_len, gboolean is_save2trans, gint update_class, guint32 ship32)
 {
 	qq_data *qd;
-	guint8 *encrypted_data;
+	guint8 *encrypted;
 	gint encrypted_len;
 	gint bytes_sent;
 
@@ -1095,18 +1135,18 @@
 	g_return_val_if_fail(data != NULL && data_len > 0, -1);
 
 	/* at most 16 bytes more */
-	encrypted_data = g_newa(guint8, data_len + 16);
-	encrypted_len = qq_encrypt(encrypted_data, data, data_len, qd->session_key);
+	encrypted = g_newa(guint8, data_len + 16);
+	encrypted_len = qq_encrypt(encrypted, data, data_len, qd->session_key);
 	if (encrypted_len < 16) {
 		purple_debug_error("QQ_ENCRYPT", "Error len %d: [%05d] 0x%04X %s\n",
 				encrypted_len, seq, cmd, qq_get_cmd_desc(cmd));
 		return -1;
 	}
 
-	bytes_sent = packet_send_out(gc, cmd, seq, encrypted_data, encrypted_len);
+	bytes_sent = packet_send_out(gc, cmd, seq, encrypted, encrypted_len);
 
 	if (is_save2trans)  {
-		qq_trans_add_client_cmd(gc, cmd, seq, encrypted_data, encrypted_len,
+		qq_trans_add_client_cmd(gc, cmd, seq, encrypted, encrypted_len,
 				update_class, ship32);
 	}
 	return bytes_sent;
@@ -1159,7 +1199,7 @@
 gint qq_send_server_reply(PurpleConnection *gc, guint16 cmd, guint16 seq, guint8 *data, gint data_len)
 {
 	qq_data *qd;
-	guint8 *encrypted_data;
+	guint8 *encrypted;
 	gint encrypted_len;
 	gint bytes_sent;
 
@@ -1172,16 +1212,16 @@
 				seq, qq_get_cmd_desc(cmd), cmd, data_len);
 #endif
 	/* at most 16 bytes more */
-	encrypted_data = g_newa(guint8, data_len + 16);
-	encrypted_len = qq_encrypt(encrypted_data, data, data_len, qd->session_key);
+	encrypted = g_newa(guint8, data_len + 16);
+	encrypted_len = qq_encrypt(encrypted, data, data_len, qd->session_key);
 	if (encrypted_len < 16) {
 		purple_debug_error("QQ_ENCRYPT", "Error len %d: [%05d] 0x%04X %s\n",
 				encrypted_len, seq, cmd, qq_get_cmd_desc(cmd));
 		return -1;
 	}
 
-	bytes_sent = packet_send_out(gc, cmd, seq, encrypted_data, encrypted_len);
-	qq_trans_add_server_reply(gc, cmd, seq, encrypted_data, encrypted_len);
+	bytes_sent = packet_send_out(gc, cmd, seq, encrypted, encrypted_len);
+	qq_trans_add_server_reply(gc, cmd, seq, encrypted, encrypted_len);
 
 	return bytes_sent;
 }
@@ -1192,7 +1232,7 @@
 	qq_data *qd;
 	guint8 *buf;
 	gint buf_len;
-	guint8 *encrypted_data;
+	guint8 *encrypted;
 	gint encrypted_len;
 	gint bytes_sent;
 	guint16 seq;
@@ -1217,17 +1257,17 @@
 	qd->send_seq++;
 	seq = qd->send_seq;
 
-	/* Encrypt to encrypted_data with session_key */
+	/* Encrypt to encrypted with session_key */
 	/* at most 16 bytes more */
-	encrypted_data = g_newa(guint8, buf_len + 16);
-	encrypted_len = qq_encrypt(encrypted_data, buf, buf_len, qd->session_key);
+	encrypted = g_newa(guint8, buf_len + 16);
+	encrypted_len = qq_encrypt(encrypted, buf, buf_len, qd->session_key);
 	if (encrypted_len < 16) {
 		purple_debug_error("QQ_ENCRYPT", "Error len %d: [%05d] %s (0x%02X)\n",
 				encrypted_len, seq, qq_get_room_cmd_desc(room_cmd), room_cmd);
 		return -1;
 	}
 
-	bytes_sent = packet_send_out(gc, QQ_CMD_ROOM, seq, encrypted_data, encrypted_len);
+	bytes_sent = packet_send_out(gc, QQ_CMD_ROOM, seq, encrypted, encrypted_len);
 #if 1
 		/* qq_show_packet("send_room_cmd", buf, buf_len); */
 		purple_debug_info("QQ",
@@ -1235,7 +1275,7 @@
 				seq, qq_get_room_cmd_desc(room_cmd), room_cmd, room_id, buf_len);
 #endif
 
-	qq_trans_add_room_cmd(gc, seq, room_cmd, room_id, encrypted_data, encrypted_len,
+	qq_trans_add_room_cmd(gc, seq, room_cmd, room_id, encrypted, encrypted_len,
 			update_class, ship32);
 	return bytes_sent;
 }
@@ -1243,18 +1283,21 @@
 gint qq_send_room_cmd_mess(PurpleConnection *gc, guint8 room_cmd, guint32 room_id,
 		guint8 *data, gint data_len, gint update_class, guint32 ship32)
 {
+	g_return_val_if_fail(room_cmd > 0, -1);
 	return send_room_cmd(gc, room_cmd, room_id, data, data_len, update_class, ship32);
 }
 
 gint qq_send_room_cmd(PurpleConnection *gc, guint8 room_cmd, guint32 room_id,
 		guint8 *data, gint data_len)
 {
+	g_return_val_if_fail(room_cmd > 0 && room_id > 0, -1);
 	return send_room_cmd(gc, room_cmd, room_id, data, data_len, 0, 0);
 }
 
 gint qq_send_room_cmd_noid(PurpleConnection *gc, guint8 room_cmd,
 		guint8 *data, gint data_len)
 {
+	g_return_val_if_fail(room_cmd > 0, -1);
 	return send_room_cmd(gc, room_cmd, 0, data, data_len, 0, 0);
 }
 
--- a/libpurple/protocols/qq/qq_network.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/qq_network.h	Thu Nov 06 03:20:05 2008 +0000
@@ -30,7 +30,7 @@
 
 #include "qq.h"
 
-#define QQ_CONNECT_STEPS    3	/* steps in connection */
+#define QQ_CONNECT_STEPS    4	/* steps in connection */
 
 gboolean qq_connect_later(gpointer data);
 void qq_disconnect(PurpleConnection *gc);
--- a/libpurple/protocols/qq/qq_process.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/qq_process.c	Thu Nov 06 03:20:05 2008 +0000
@@ -30,27 +30,22 @@
 #include "buddy_list.h"
 #include "buddy_opt.h"
 #include "group_info.h"
-#include "group_free.h"
 #include "char_conv.h"
 #include "qq_crypt.h"
 
-#include "group_conv.h"
-#include "group_find.h"
 #include "group_internal.h"
 #include "group_im.h"
 #include "group_info.h"
 #include "group_join.h"
 #include "group_opt.h"
-#include "group_search.h"
 
-#include "header_info.h"
+#include "qq_define.h"
 #include "qq_base.h"
 #include "im.h"
 #include "qq_process.h"
 #include "packet_parse.h"
 #include "qq_network.h"
 #include "qq_trans.h"
-#include "sys_msg.h"
 #include "utils.h"
 
 enum {
@@ -60,10 +55,10 @@
 };
 
 /* default process, decrypt and dump */
-static void process_cmd_unknow(PurpleConnection *gc,const gchar *title, guint8 *data, gint data_len, guint16 cmd, guint16 seq)
+static void process_unknow_cmd(PurpleConnection *gc,const gchar *title, guint8 *data, gint data_len, guint16 cmd, guint16 seq)
 {
 	qq_data *qd;
-	gchar *msg_utf8 = NULL;
+	gchar *msg;
 
 	g_return_if_fail(data != NULL && data_len != 0);
 
@@ -76,11 +71,390 @@
 			">>> [%d] %s -> [default] decrypt and dump",
 			seq, qq_get_cmd_desc(cmd));
 
-	msg_utf8 = try_dump_as_gbk(data, data_len);
-	if (msg_utf8 != NULL) {
-		purple_notify_info(gc, _("QQ Error"), title, msg_utf8);
-		g_free(msg_utf8);
+	msg = g_strdup_printf("Unknow command 0x%02X, %s", cmd, qq_get_cmd_desc(cmd));
+	purple_notify_info(gc, _("QQ Error"), title, msg);
+	g_free(msg);
+}
+
+/* parse the reply to send_im */
+static void do_im_ack(guint8 *data, gint data_len, PurpleConnection *gc)
+{
+	qq_data *qd;
+
+	g_return_if_fail(data != NULL && data_len != 0);
+
+	qd = gc->proto_data;
+
+	if (data[0] != 0) {
+		purple_debug_warning("QQ", "Failed sent IM\n");
+		purple_notify_error(gc, _("Error"), _("Failed to send IM."), NULL);
+		return;
+	}
+
+	purple_debug_info("QQ", "OK sent IM\n");
+}
+
+static void do_server_news(guint8 *data, gint data_len, PurpleConnection *gc)
+{
+	qq_data *qd = (qq_data *) gc->proto_data;
+	gint bytes;
+	gchar *title, *brief, *url;
+	gchar *content;
+
+	g_return_if_fail(data != NULL && data_len != 0);
+
+	/* qq_show_packet("Rcv news", data, data_len); */
+
+	bytes = 4;	/* skip unknown 4 bytes */
+
+	bytes += qq_get_vstr(&title, QQ_CHARSET_DEFAULT, data + bytes);
+	bytes += qq_get_vstr(&brief, QQ_CHARSET_DEFAULT, data + bytes);
+	bytes += qq_get_vstr(&url, QQ_CHARSET_DEFAULT, data + bytes);
+
+	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);
+	} else {
+		purple_debug_info("QQ", "QQ Server news:\n%s\n", content);
+	}
+	g_free(title);
+	g_free(brief);
+	g_free(url);
+	g_free(content);
+}
+
+static void do_msg_sys_30(PurpleConnection *gc, guint8 *data, gint data_len)
+{
+	gint len;
+	guint8 reply;
+	gchar **segments, *msg_utf8;
+
+	g_return_if_fail(data != NULL && data_len != 0);
+
+	len = data_len;
+
+	if (NULL == (segments = split_data(data, len, "\x2f", 2)))
+		return;
+
+	reply = strtol(segments[0], NULL, 10);
+	if (reply == 1)
+		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);
+}
+
+static void do_msg_sys_4c(PurpleConnection *gc, guint8 *data, gint data_len)
+{
+	gint bytes;
+	gint msg_len;
+	GString *content;
+	gchar *msg = NULL;
+
+	g_return_if_fail(data != NULL && data_len > 0);
+
+	bytes = 6; /* skip 0x(06 00 01 1e 01 1c)*/
+
+	content = g_string_new("");
+	while (bytes < data_len) {
+		msg_len = qq_get_vstr(&msg, QQ_CHARSET_DEFAULT, data + bytes);
+		g_string_append(content, msg);
+		g_string_append(content, "\n");
+		g_free(msg);
+
+		if (msg_len <= 1) {
+			break;
+		}
+		bytes += msg_len;
+	}
+	if (bytes != data_len) {
+		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);
+	g_string_free(content, FALSE);
+}
+
+static const gchar *get_im_type_desc(gint type)
+{
+	switch (type) {
+		case QQ_MSG_TO_BUDDY:
+			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_ADD_TO_QUN:
+			return "QQ_MSG_ADD_TO_QUN";
+		case QQ_MSG_DEL_FROM_QUN:
+			return "QQ_MSG_DEL_FROM_QUN";
+		case QQ_MSG_APPLY_ADD_TO_QUN:
+			return "QQ_MSG_APPLY_ADD_TO_QUN";
+		case QQ_MSG_CREATE_QUN:
+			return "QQ_MSG_CREATE_QUN";
+		case QQ_MSG_SYS_30:
+			return "QQ_MSG_SYS_30";
+		case QQ_MSG_SYS_4C:
+			return "QQ_MSG_SYS_4C";
+		case QQ_MSG_APPROVE_APPLY_ADD_TO_QUN:
+			return "QQ_MSG_APPROVE_APPLY_ADD_TO_QUN";
+		case QQ_MSG_REJCT_APPLY_ADD_TO_QUN:
+			return "QQ_MSG_REJCT_APPLY_ADD_TO_QUN";
+		case QQ_MSG_TEMP_QUN_IM:
+			return "QQ_MSG_TEMP_QUN_IM";
+		case QQ_MSG_QUN_IM:
+			return "QQ_MSG_QUN_IM";
+		case QQ_MSG_NEWS:
+			return "QQ_MSG_NEWS";
+		case QQ_MSG_EXTEND:
+			return "QQ_MSG_EXTEND";
+		case QQ_MSG_EXTEND_85:
+			return "QQ_MSG_EXTEND_85";
+		default:
+			return "QQ_MSG_UNKNOWN";
+	}
+}
+
+/* I receive a message, mainly it is text msg,
+ * but we need to proess other types (group etc) */
+static void process_private_msg(guint8 *data, gint data_len, guint16 seq, PurpleConnection *gc)
+{
+	qq_data *qd;
+	gint bytes;
+
+	struct {
+		guint32 uid_from;
+		guint32 uid_to;
+		guint32 seq;
+		struct in_addr ip_from;
+		guint16 port_from;
+		guint16 msg_type;
+	} header;
+
+	g_return_if_fail(data != NULL && data_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+
+	if (data_len < 16) {	/* we need to ack with the first 16 bytes */
+		purple_debug_error("QQ", "MSG is too short\n");
+		return;
+	} else {
+		/* when we receive a message,
+		 * we send an ACK which is the first 16 bytes of incoming packet */
+		qq_send_server_reply(gc, QQ_CMD_RECV_IM, seq, data, 16);
+	}
+
+	/* check len first */
+	if (data_len < 20) {	/* length of im_header */
+		purple_debug_error("QQ", "Invald MSG header, len %d < 20\n", data_len);
+		return;
 	}
+
+	bytes = 0;
+	bytes += qq_get32(&(header.uid_from), data + bytes);
+	bytes += qq_get32(&(header.uid_to), data + bytes);
+	bytes += qq_get32(&(header.seq), data + bytes);
+	/* if the message is delivered via server, it is server IP/port */
+	bytes += qq_getIP(&(header.ip_from), data + bytes);
+	bytes += qq_get16(&(header.port_from), data + bytes);
+	bytes += qq_get16(&(header.msg_type), data + bytes);
+	/* 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);
+		return;
+	}
+
+	/* check bytes */
+	if (bytes >= data_len - 1) {
+		purple_debug_warning("QQ", "Empty MSG\n");
+		return;
+	}
+
+	switch (header.msg_type) {
+		case QQ_MSG_NEWS:
+			do_server_news(data + bytes, data_len - bytes, gc);
+			break;
+		case QQ_MSG_EXTEND:
+		case QQ_MSG_EXTEND_85:
+			purple_debug_info("QQ", "MSG from buddy [%d]\n", header.uid_from);
+			qq_process_extend_im(gc, data + bytes, data_len - bytes);
+			break;
+		case QQ_MSG_TO_UNKNOWN:
+		case QQ_MSG_TO_BUDDY:
+			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_TEMP_QUN_IM:
+		case QQ_MSG_QUN_IM:
+			purple_debug_info("QQ", "MSG from room [%d]\n", header.uid_from);
+			qq_process_room_im(data + bytes, data_len - bytes, header.uid_from, gc, header.msg_type);
+			break;
+		case QQ_MSG_ADD_TO_QUN:
+			purple_debug_info("QQ", "Notice from [%d], Added\n", header.uid_from);
+			/* uid_from is group id
+			 * we need this to create a dummy group and add to blist */
+			qq_process_room_buddy_joined(data + bytes, data_len - bytes, header.uid_from, gc);
+			break;
+		case QQ_MSG_DEL_FROM_QUN:
+			purple_debug_info("QQ", "Notice from room [%d], Removed\n", header.uid_from);
+			/* uid_from is group id */
+			qq_process_room_buddy_removed(data + bytes, data_len - bytes, header.uid_from, gc);
+			break;
+		case QQ_MSG_APPLY_ADD_TO_QUN:
+			purple_debug_info("QQ", "Notice from room [%d], Joined\n", header.uid_from);
+			/* uid_from is group id */
+			qq_process_room_buddy_request_join(data + bytes, data_len - bytes, header.uid_from, gc);
+			break;
+		case QQ_MSG_APPROVE_APPLY_ADD_TO_QUN:
+			purple_debug_info("QQ", "Notice from room [%d], Confirm add in\n",
+					header.uid_from);
+			/* uid_from is group id */
+			qq_process_room_buddy_approved(data + bytes, data_len - bytes, header.uid_from, gc);
+			break;
+		case QQ_MSG_REJCT_APPLY_ADD_TO_QUN:
+			purple_debug_info("QQ", "Notice from room [%d], Refuse add in\n",
+					header.uid_from);
+			/* uid_from is group id */
+			qq_process_room_buddy_rejected(data + bytes, data_len - bytes, header.uid_from, gc);
+			break;
+		case QQ_MSG_SYS_30:
+			do_msg_sys_30(gc, data + bytes, data_len - bytes);
+			break;
+		case QQ_MSG_SYS_4C:
+			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",
+					header.uid_from, get_im_type_desc(header.msg_type), header.msg_type);
+			qq_show_packet("Unknown MSG type", data, data_len);
+			break;
+	}
+}
+
+/* Send ACK if the sys message needs an ACK */
+static void request_server_ack(PurpleConnection *gc, gchar *funct_str, gchar *from, guint16 seq)
+{
+	qq_data *qd;
+	guint8 *raw_data;
+	gint bytes;
+	guint8 bar;
+
+	g_return_if_fail(funct_str != NULL && from != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+
+	bar = 0x1e;
+	raw_data = g_newa(guint8, strlen(funct_str) + strlen(from) + 16);
+
+	bytes = 0;
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)funct_str, strlen(funct_str));
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)from, strlen(from));
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_put16(raw_data + bytes, seq);
+
+	qq_send_server_reply(gc, QQ_CMD_ACK_SYS_MSG, 0, raw_data, bytes);
+}
+
+static void do_server_notice(PurpleConnection *gc, gchar *from, gchar *to,
+		guint8 *data, gint data_len)
+{
+	qq_data *qd = (qq_data *) gc->proto_data;
+	gchar *msg, *msg_utf8;
+	gchar *title, *content;
+
+	g_return_if_fail(from != NULL && to != NULL && data_len > 0);
+
+	msg = g_strndup((gchar *)data, data_len);
+	msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT);
+	g_free(msg);
+	if (msg_utf8 == NULL) {
+		purple_debug_error("QQ", "Recv NULL sys msg from %s to %s, discard\n",
+				from, to);
+		return;
+	}
+
+	title = g_strdup_printf(_("From %s:"), from);
+	content = g_strdup_printf(_("Server notice From %s: \n%s"), from, msg_utf8);
+
+	if (qd->is_show_notice) {
+		qq_got_attention(gc, content);
+	} else {
+		purple_debug_info("QQ", "QQ Server notice from %s:\n%s", from, msg_utf8);
+	}
+	g_free(msg_utf8);
+	g_free(title);
+	g_free(content);
+}
+
+static void process_server_msg(PurpleConnection *gc, guint8 *data, gint data_len, guint16 seq)
+{
+	qq_data *qd;
+	guint8 *data_str;
+	gchar **segments;
+	gchar *funct_str, *from, *to;
+	gint bytes, funct;
+
+	g_return_if_fail(data != NULL && data_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+
+	data_str = g_newa(guint8, data_len + 1);
+	g_memmove(data_str, data, data_len);
+	data_str[data_len] = 0x00;
+
+	segments = g_strsplit_set((gchar *) data_str, "\x1f", 0);
+	g_return_if_fail(segments != NULL);
+	if (g_strv_length(segments) < 3) {
+		purple_debug_warning("QQ", "Server message segments is less than 3\n");
+		g_strfreev(segments);
+		return;
+	}
+
+	bytes = 0;
+	funct_str = segments[0];
+	bytes += strlen(funct_str) + 1;
+	from = segments[1];
+	bytes += strlen(from) + 1;
+	to = segments[2];
+	bytes += strlen(to) + 1;
+
+	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 */
+		purple_debug_error("QQ", "Recv sys msg to [%s], not me!, discard\n", to);
+		g_strfreev(segments);
+		return;
+	}
+
+	funct = strtol(funct_str, NULL, 10);
+	switch (funct) {
+		case QQ_SERVER_BUDDY_ADDED:
+		case QQ_SERVER_BUDDY_ADD_REQUEST:
+		case QQ_SERVER_BUDDY_ADDED_ME:
+		case QQ_SERVER_BUDDY_REJECTED_ME:
+		case QQ_SERVER_BUDDY_ADD_REQUEST_EX:
+		case QQ_SERVER_BUDDY_ADDING_EX:
+		case QQ_SERVER_BUDDY_ADDED_ANSWER:
+		case QQ_SERVER_BUDDY_ADDED_EX:
+			qq_process_buddy_from_server(gc,  funct, from, to, data + bytes, data_len - bytes);
+			break;
+		case QQ_SERVER_NOTICE:
+			do_server_notice(gc, from, to, data + bytes, data_len - bytes);
+			break;
+		case QQ_SERVER_NEW_CLIENT:
+			purple_debug_warning("QQ", "QQ Server has newer client version\n");
+			break;
+		default:
+			qq_show_packet("Unknown sys msg", data, data_len);
+			purple_debug_warning("QQ", "Recv unknown sys msg code: %s\n", funct_str);
+			break;
+	}
+	g_strfreev(segments);
 }
 
 void qq_proc_server_cmd(PurpleConnection *gc, guint16 cmd, guint16 seq, guint8 *rcved, gint rcved_len)
@@ -113,16 +487,16 @@
 	/* now process the packet */
 	switch (cmd) {
 		case QQ_CMD_RECV_IM:
-			qq_process_recv_im(data, data_len, seq, gc);
+			process_private_msg(data, data_len, seq, gc);
 			break;
 		case QQ_CMD_RECV_MSG_SYS:
-			qq_process_msg_sys(data, data_len, seq, gc);
+			process_server_msg(gc, data, data_len, seq);
 			break;
 		case QQ_CMD_BUDDY_CHANGE_STATUS:
 			qq_process_buddy_change_status(data, data_len, gc);
 			break;
 		default:
-			process_cmd_unknow(gc, _("Unknow SERVER CMD"), data, data_len, cmd, seq);
+			process_unknow_cmd(gc, _("Unknow SERVER CMD"), data, data_len, cmd, seq);
 			break;
 	}
 }
@@ -150,36 +524,25 @@
 void qq_update_room(PurpleConnection *gc, guint8 room_cmd, guint32 room_id)
 {
 	qq_data *qd;
-	qq_group *group;
 	gint ret;
 
 	g_return_if_fail (gc != NULL && gc->proto_data != NULL);
 	qd = (qq_data *) gc->proto_data;
 
-	group = qq_room_search_id(gc, room_id);
-	if (group == NULL && room_id <= 0) {
-		purple_debug_info("QQ", "No room, nothing update\n");
-		return;
-	}
-	if (group == NULL ) {
-		purple_debug_warning("QQ", "Failed search room id [%d]\n", room_id);
-		return;
-	}
-
 	switch (room_cmd) {
 		case 0:
-			qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_INFO, group->id, NULL, 0,
+			qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_INFO, room_id, NULL, 0,
 					QQ_CMD_CLASS_UPDATE_ROOM, 0);
 			break;
 		case QQ_ROOM_CMD_GET_INFO:
-			ret = qq_request_room_get_buddies(gc, group, QQ_CMD_CLASS_UPDATE_ROOM);
+			ret = qq_request_room_get_buddies(gc, room_id, QQ_CMD_CLASS_UPDATE_ROOM);
 			if (ret <= 0) {
-				qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_ONLINES, group->id, NULL, 0,
+				qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_ONLINES, room_id, NULL, 0,
 						QQ_CMD_CLASS_UPDATE_ROOM, 0);
 			}
 			break;
 		case QQ_ROOM_CMD_GET_BUDDIES:
-			qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_ONLINES, group->id, NULL, 0,
+			qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_ONLINES, room_id, NULL, 0,
 					QQ_CMD_CLASS_UPDATE_ROOM, 0);
 			break;
 		case QQ_ROOM_CMD_GET_ONLINES:
@@ -189,43 +552,46 @@
 	}
 }
 
-static void update_all_rooms(PurpleConnection *gc, guint8 room_cmd, guint32 room_id)
+void qq_update_all_rooms(PurpleConnection *gc, guint8 room_cmd, guint32 room_id)
 {
 	qq_data *qd;
 	gboolean is_new_turn = FALSE;
-	qq_group *next_group;
+	guint32 next_id;
 
 	g_return_if_fail (gc != NULL && gc->proto_data != NULL);
 	qd = (qq_data *) gc->proto_data;
 
-	next_group = qq_room_get_next(gc, room_id);
-	if (next_group == NULL && room_id <= 0) {
-		purple_debug_info("QQ", "No room. Finished update\n");
-		return;
-	}
-	if (next_group == NULL ) {
-		is_new_turn = TRUE;
-		next_group = qq_room_get_next(gc, 0);
-		g_return_if_fail(next_group != NULL);
+	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);
+
+	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);
+		} else {
+			purple_debug_info("QQ", "No room. Finished update\n");
+			return;
+		}
 	}
 
 	switch (room_cmd) {
 		case 0:
-			qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_INFO, next_group->id, NULL, 0,
+			qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_INFO, next_id, NULL, 0,
 					QQ_CMD_CLASS_UPDATE_ALL, 0);
 			break;
 		case QQ_ROOM_CMD_GET_INFO:
 			if (!is_new_turn) {
-				qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_INFO, next_group->id, NULL, 0,
+				qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_INFO, next_id, NULL, 0,
 						QQ_CMD_CLASS_UPDATE_ALL, 0);
 			} else {
-				qq_request_room_get_buddies(gc, next_group, QQ_CMD_CLASS_UPDATE_ALL);
+				qq_request_room_get_buddies(gc, next_id, QQ_CMD_CLASS_UPDATE_ALL);
 			}
 			break;
 		case QQ_ROOM_CMD_GET_BUDDIES:
 			/* last command */
 			if (!is_new_turn) {
-				qq_request_room_get_buddies(gc, next_group, QQ_CMD_CLASS_UPDATE_ALL);
+				qq_request_room_get_buddies(gc, next_id, QQ_CMD_CLASS_UPDATE_ALL);
 			} else {
 				purple_debug_info("QQ", "Finished update\n");
 			}
@@ -244,57 +610,63 @@
 
 	switch (cmd) {
 		case 0:
-			qq_request_buddy_info(gc, qd->uid, QQ_CMD_CLASS_UPDATE_ALL, QQ_BUDDY_INFO_UPDATE_ONLY);
+			qq_request_buddy_info(gc, qd->uid, QQ_CMD_CLASS_UPDATE_ALL, 0);
 			break;
 		case QQ_CMD_GET_BUDDY_INFO:
 			qq_request_change_status(gc, QQ_CMD_CLASS_UPDATE_ALL);
 			break;
 		case QQ_CMD_CHANGE_STATUS:
-			qq_request_get_buddies_list(gc, 0, QQ_CMD_CLASS_UPDATE_ALL);
+			qq_request_get_buddies(gc, 0, QQ_CMD_CLASS_UPDATE_ALL);
 			break;
 		case QQ_CMD_GET_BUDDIES_LIST:
 			qq_request_get_buddies_and_rooms(gc, 0, QQ_CMD_CLASS_UPDATE_ALL);
 			break;
 		case QQ_CMD_GET_BUDDIES_AND_ROOMS:
-			qq_request_get_buddies_level(gc, QQ_CMD_CLASS_UPDATE_ALL);
+			if (qd->client_version >= 2007) {
+				/* QQ2007/2008 can not get buddies level*/
+				qq_request_get_buddies_online(gc, 0, QQ_CMD_CLASS_UPDATE_ALL);
+			} else {
+				qq_request_get_buddies_level(gc, QQ_CMD_CLASS_UPDATE_ALL);
+			}
 			break;
 		case QQ_CMD_GET_LEVEL:
 			qq_request_get_buddies_online(gc, 0, QQ_CMD_CLASS_UPDATE_ALL);
 			break;
 		case QQ_CMD_GET_BUDDIES_ONLINE:
 			/* last command */
-			update_all_rooms(gc, 0, 0);
+			qq_update_all_rooms(gc, 0, 0);
 			break;
 		default:
 			break;
 	}
+	qd->online_last_update = time(NULL);
 }
 
 static void update_all_rooms_online(PurpleConnection *gc, guint8 room_cmd, guint32 room_id)
 {
 	qq_data *qd;
-	qq_group *next_group;
+	guint32 next_id;
 
 	g_return_if_fail (gc != NULL && gc->proto_data != NULL);
 	qd = (qq_data *) gc->proto_data;
 
-	next_group = qq_room_get_next_conv(gc, room_id);
-	if (next_group == NULL && room_id <= 0) {
+	next_id = qq_room_get_next_conv(gc, room_id);
+	if (next_id <= 0 && room_id <= 0) {
 		purple_debug_info("QQ", "No room in conversation, no update online buddies\n");
 		return;
 	}
-	if (next_group == NULL ) {
+	if (next_id <= 0 ) {
 		purple_debug_info("QQ", "finished update rooms' online buddies\n");
 		return;
 	}
 
 	switch (room_cmd) {
 		case 0:
-			qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_ONLINES, next_group->id, NULL, 0,
+			qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_ONLINES, next_id, NULL, 0,
 					QQ_CMD_CLASS_UPDATE_ALL, 0);
 			break;
 		case QQ_ROOM_CMD_GET_ONLINES:
-			qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_ONLINES, next_group->id, NULL, 0,
+			qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_ONLINES, next_id, NULL, 0,
 					QQ_CMD_CLASS_UPDATE_ALL, 0);
 			break;
 		default:
@@ -304,6 +676,10 @@
 
 void qq_update_online(PurpleConnection *gc, guint16 cmd)
 {
+	qq_data *qd;
+	g_return_if_fail (gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
 	switch (cmd) {
 		case 0:
 			qq_request_get_buddies_online(gc, 0, QQ_CMD_CLASS_UPDATE_ONLINE);
@@ -315,16 +691,17 @@
 		default:
 			break;
 	}
+	qd->online_last_update = time(NULL);
 }
 
-void qq_proc_room_cmd(PurpleConnection *gc, guint16 seq,
+void qq_proc_room_cmds(PurpleConnection *gc, guint16 seq,
 		guint8 room_cmd, guint32 room_id, guint8 *rcved, gint rcved_len,
 		gint update_class, guint32 ship32)
 {
 	qq_data *qd;
 	guint8 *data;
 	gint data_len;
-	qq_group *group;
+	qq_room_data *rmd;
 	gint bytes;
 	guint8 reply_cmd, reply;
 
@@ -355,13 +732,6 @@
 		return;
 	}
 
-	group = qq_room_search_id(gc, room_id);
-	if (group == NULL) {
-		purple_debug_warning("QQ",
-			"Missing room id in [%05d], 0x%02X %s for %d, len %d\n",
-			seq, room_cmd, qq_get_room_cmd_desc(room_cmd), room_id, rcved_len);
-	}
-
 	bytes = 0;
 	bytes += qq_get8(&reply_cmd, data + bytes);
 	bytes += qq_get8(&reply, data + bytes);
@@ -375,17 +745,17 @@
 
 	/* now process the packet */
 	if (reply != QQ_ROOM_CMD_REPLY_OK) {
-		if (group != NULL) {
-			qq_set_pending_id(&qd->joining_groups, group->ext_id, FALSE);
-		}
-
 		switch (reply) {	/* this should be all errors */
 		case QQ_ROOM_CMD_REPLY_NOT_MEMBER:
-			if (group != NULL) {
+			rmd = qq_room_data_find(gc, room_id);
+			if (rmd == NULL) {
 				purple_debug_warning("QQ",
-					   _("You are not a member of QQ Qun \"%s\"\n"), group->title_utf8);
-				group->my_role = QQ_ROOM_ROLE_NO;
-				qq_group_refresh(gc, group);
+						"Missing room id in [%05d], 0x%02X %s for %d, len %d\n",
+						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);
+				rmd->my_role = QQ_ROOM_ROLE_NO;
 			}
 			break;
 		case QQ_ROOM_CMD_REPLY_SEARCH_ERROR:
@@ -402,7 +772,7 @@
 	/* seems ok so far, so we process the reply according to sub_cmd */
 	switch (reply_cmd) {
 	case QQ_ROOM_CMD_GET_INFO:
-		qq_process_room_cmd_get_info(data + bytes, data_len - bytes, gc);
+		qq_process_room_cmd_get_info(data + bytes, data_len - bytes, ship32, gc);
 		break;
 	case QQ_ROOM_CMD_CREATE:
 		qq_group_process_create_group_reply(data + bytes, data_len - bytes, gc);
@@ -417,7 +787,7 @@
 		qq_group_process_activate_group_reply(data + bytes, data_len - bytes, gc);
 		break;
 	case QQ_ROOM_CMD_SEARCH:
-		qq_process_group_cmd_search_group(data + bytes, data_len - bytes, gc);
+		qq_process_room_search(gc, data + bytes, data_len - bytes, ship32);
 		break;
 	case QQ_ROOM_CMD_JOIN:
 		qq_process_group_cmd_join_group(data + bytes, data_len - bytes, gc);
@@ -429,19 +799,13 @@
 		qq_process_group_cmd_exit_group(data + bytes, data_len - bytes, gc);
 		break;
 	case QQ_ROOM_CMD_SEND_MSG:
-		qq_process_group_cmd_im(data + bytes, data_len - bytes, gc);
+		qq_process_room_send_im(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);
-		if (group != NULL)
-			qq_group_conv_refresh_online_member(gc, group);
 		break;
 	case QQ_ROOM_CMD_GET_BUDDIES:
 		qq_process_room_cmd_get_buddies(data + bytes, data_len - bytes, gc);
-		if (group != NULL) {
-			group->is_got_info = TRUE;
-			qq_group_conv_refresh_online_member(gc, group);
-		}
 		break;
 	default:
 		purple_debug_warning("QQ", "Unknow room cmd 0x%02X %s\n",
@@ -451,9 +815,8 @@
 	if (update_class == QQ_CMD_CLASS_NONE)
 		return;
 
-	purple_debug_info("QQ", "Update class %d\n", update_class);
 	if (update_class == QQ_CMD_CLASS_UPDATE_ALL) {
-		update_all_rooms(gc, room_cmd, room_id);
+		qq_update_all_rooms(gc, room_cmd, room_id);
 		return;
 	}
 	if (update_class == QQ_CMD_CLASS_UPDATE_ONLINE) {
@@ -465,58 +828,159 @@
 	}
 }
 
-void qq_proc_login_cmd(PurpleConnection *gc, guint8 *rcved, gint rcved_len)
+guint8 qq_proc_login_cmds(PurpleConnection *gc,  guint16 cmd, guint16 seq,
+		guint8 *rcved, gint rcved_len, gint update_class, guint32 ship32)
 {
 	qq_data *qd;
-	guint8 *data;
-	gint data_len;
-	guint ret_8;
+	guint8 *data = NULL;
+	gint data_len = 0;
+	guint ret_8 = QQ_LOGIN_REPLY_ERR;
 
-	g_return_if_fail (gc != NULL && gc->proto_data != NULL);
+	g_return_val_if_fail (gc != NULL && gc->proto_data != NULL, QQ_LOGIN_REPLY_ERR);
 	qd = (qq_data *) gc->proto_data;
 
+	g_return_val_if_fail(rcved_len > 0, QQ_LOGIN_REPLY_ERR);
 	data = g_newa(guint8, rcved_len);
-	/* May use password_twice_md5 in the past version like QQ2005*/
-	data_len = qq_decrypt(data, rcved, rcved_len, qd->inikey);
-	if (data_len >= 0) {
+
+	switch (cmd) {
+		case QQ_CMD_TOKEN:
+			if (qq_process_token(gc, rcved, rcved_len) == QQ_LOGIN_REPLY_OK) {
+				if (qd->client_version >= 2007) {
+					qq_request_token_ex(gc);
+				} else {
+					qq_request_login(gc);
+				}
+				return QQ_LOGIN_REPLY_OK;
+			}
+			return QQ_LOGIN_REPLY_ERR;
+		case QQ_CMD_GET_SERVER:
+		case QQ_CMD_TOKEN_EX:
+			data_len = qq_decrypt(data, rcved, rcved_len, qd->ld.random_key);
+			break;
+		case QQ_CMD_CHECK_PWD:
+			/* May use password_twice_md5 in the past version like QQ2005 */
+			data_len = qq_decrypt(data, rcved, rcved_len, qd->ld.random_key);
+			if (data_len >= 0) {
+				purple_debug_warning("QQ", "Decrypt login packet by random_key, %d bytes\n", data_len);
+			} else {
+				data_len = qq_decrypt(data, rcved, rcved_len, qd->ld.pwd_twice_md5);
+				if (data_len >= 0) {
+					purple_debug_warning("QQ", "Decrypt login packet by pwd_twice_md5, %d bytes\n", data_len);
+				}
+			}
+			break;
+		case QQ_CMD_LOGIN:
+		default:
+			if (qd->client_version >= 2007) {
+				data_len = qq_decrypt(data, rcved, rcved_len, qd->ld.pwd_twice_md5);
+				if (data_len >= 0) {
+					purple_debug_warning("QQ", "Decrypt login packet by pwd_twice_md5\n");
+				} else {
+					data_len = qq_decrypt(data, rcved, rcved_len, qd->ld.login_key);
+					if (data_len >= 0) {
+						purple_debug_warning("QQ", "Decrypt login packet by login_key\n");
+					}
+				}
+			} else {
+				/* May use password_twice_md5 in the past version like QQ2005 */
+				data_len = qq_decrypt(data, rcved, rcved_len, qd->ld.random_key);
+				if (data_len >= 0) {
+					purple_debug_warning("QQ", "Decrypt login packet by random_key\n");
+				} else {
+					data_len = qq_decrypt(data, rcved, rcved_len, qd->ld.pwd_twice_md5);
+					if (data_len >= 0) {
+						purple_debug_warning("QQ", "Decrypt login packet by pwd_twice_md5\n");
+					}
+				}
+			}
+			break;
+	}
+
+	if (data_len < 0) {
 		purple_debug_warning("QQ",
-				"Decrypt login reply packet with inikey, %d bytes\n", data_len);
-	} else {
-		data_len = qq_decrypt(data, rcved, rcved_len, qd->password_twice_md5);
-		if (data_len >= 0) {
-			purple_debug_warning("QQ",
-				"Decrypt login reply packet with password_twice_md5, %d bytes\n", data_len);
-		} else {
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				"Can not decrypt login cmd, [%05d], 0x%04X %s, len %d\n",
+				seq, cmd, qq_get_cmd_desc(cmd), rcved_len);
+		qq_show_packet("Can not decrypted", rcved, rcved_len);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
 				_("Can not decrypt login reply"));
-			return;
-		}
+		return QQ_LOGIN_REPLY_ERR;
 	}
 
-	ret_8 = qq_process_login_reply(gc, data, data_len);
-	if (ret_8 != QQ_LOGIN_REPLY_OK) {
-		return;
-	}
-
-	purple_debug_info("QQ", "Login repliess OK; everything is fine\n");
-
-	purple_connection_set_state(gc, PURPLE_CONNECTED);
-	qd->is_login = TRUE;	/* must be defined after sev_finish_login */
+	switch (cmd) {
+		case QQ_CMD_GET_SERVER:
+			ret_8 = qq_process_get_server(gc, data, data_len);
+			if ( ret_8 == QQ_LOGIN_REPLY_OK) {
+				qq_request_token(gc);
+			} else if ( ret_8 == QQ_LOGIN_REPLY_REDIRECT) {
+				return QQ_LOGIN_REPLY_REDIRECT;
+			}
+			break;
+		case QQ_CMD_TOKEN_EX:
+			ret_8 = qq_process_token_ex(gc, data, data_len);
+			if (ret_8 == QQ_LOGIN_REPLY_OK) {
+				qq_request_check_pwd(gc);
+			} else if (ret_8 == QQ_LOGIN_REPLY_NEXT_TOKEN_EX) {
+				qq_request_token_ex_next(gc);
+			} else if (ret_8 == QQ_LOGIN_REPLY_CAPTCHA_DLG) {
+				qq_captcha_input_dialog(gc, &(qd->captcha));
+				g_free(qd->captcha.token);
+				g_free(qd->captcha.data);
+				memset(&qd->captcha, 0, sizeof(qd->captcha));
+			}
+			break;
+		case QQ_CMD_CHECK_PWD:
+			ret_8 = qq_process_check_pwd(gc, data, data_len);
+			if (ret_8 != QQ_LOGIN_REPLY_OK) {
+				return ret_8;
+			}
+			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) {
+				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) {
+				ret_8 = qq_process_login_2007(gc, data, data_len);
+				if ( ret_8 == QQ_LOGIN_REPLY_REDIRECT) {
+                		qq_request_get_server(gc);
+                		return QQ_LOGIN_REPLY_OK;
+            	}
+			} else {
+				ret_8 = qq_process_login(gc, data, data_len);
+			}
+			if (ret_8 != QQ_LOGIN_REPLY_OK) {
+				return ret_8;
+			}
 
-	/* now initiate QQ Qun, do it first as it may take longer to finish */
-	qq_group_init(gc);
+			purple_connection_update_progress(gc, _("Logined"), QQ_CONNECT_STEPS - 1, QQ_CONNECT_STEPS);
+			purple_debug_info("QQ", "Login repliess OK; everything is fine\n");
+			purple_connection_set_state(gc, PURPLE_CONNECTED);
+			qd->is_login = TRUE;	/* must be defined after sev_finish_login */
 
-	/* Now goes on updating my icon/nickname, not showing info_window */
-	qd->modifying_face = FALSE;
+			/* now initiate QQ Qun, do it first as it may take longer to finish */
+			qq_room_data_initial(gc);
 
-	/* is_login, but we have packets before login */
-	qq_trans_process_remained(gc);
+			/* is_login, but we have packets before login */
+			qq_trans_process_remained(gc);
 
-	qq_update_all(gc, 0);
-	return;
+			qq_update_all(gc, 0);
+			break;
+		default:
+			process_unknow_cmd(gc, _("Unknow LOGIN CMD"), data, data_len, cmd, seq);
+			return QQ_LOGIN_REPLY_ERR;
+	}
+	return QQ_LOGIN_REPLY_OK;
 }
 
-void qq_proc_client_cmd(PurpleConnection *gc, guint16 cmd, guint16 seq,
+void qq_proc_client_cmds(PurpleConnection *gc, guint16 cmd, guint16 seq,
 		guint8 *rcved, gint rcved_len, gint update_class, guint32 ship32)
 {
 	qq_data *qd;
@@ -553,50 +1017,56 @@
 
 	switch (cmd) {
 		case QQ_CMD_UPDATE_INFO:
-			qq_process_modify_info_reply(data, data_len, gc);
+			qq_process_change_info(gc, data, data_len);
 			break;
-		case QQ_CMD_ADD_BUDDY_WO_AUTH:
-			qq_process_add_buddy_reply(data, data_len, seq, gc);
+		case QQ_CMD_ADD_BUDDY_NO_AUTH:
+			qq_process_add_buddy_no_auth(gc, data, data_len, ship32);
 			break;
-		case QQ_CMD_DEL_BUDDY:
-			qq_process_remove_buddy_reply(data, data_len, gc);
+		case QQ_CMD_REMOVE_BUDDY:
+			qq_process_remove_buddy(gc, data, data_len, ship32);
 			break;
-		case QQ_CMD_REMOVE_SELF:
-			qq_process_remove_self_reply(data, data_len, gc);
+		case QQ_CMD_REMOVE_ME:
+			qq_process_buddy_remove_me(gc, data, data_len, ship32);
 			break;
-		case QQ_CMD_BUDDY_AUTH:
-			qq_process_add_buddy_auth_reply(data, data_len, gc);
+		case QQ_CMD_ADD_BUDDY_AUTH:
+			qq_process_add_buddy_auth(data, data_len, gc);
 			break;
 		case QQ_CMD_GET_BUDDY_INFO:
-			qq_process_get_buddy_info(data, data_len, gc);
+			qq_process_get_buddy_info(data, data_len, ship32, gc);
 			break;
 		case QQ_CMD_CHANGE_STATUS:
-			qq_process_change_status_reply(data, data_len, gc);
+			qq_process_change_status(data, data_len, gc);
 			break;
 		case QQ_CMD_SEND_IM:
-			qq_process_send_im_reply(data, data_len, gc);
+			do_im_ack(data, data_len, gc);
 			break;
 		case QQ_CMD_KEEP_ALIVE:
-			qq_process_keep_alive(data, data_len, gc);
+			if (qd->client_version >= 2008) {
+				qq_process_keep_alive_2008(data, data_len, gc);
+			} else if (qd->client_version >= 2007) {
+				qq_process_keep_alive_2007(data, data_len, gc);
+			} else {
+				qq_process_keep_alive(data, data_len, gc);
+			}
 			break;
 		case QQ_CMD_GET_BUDDIES_ONLINE:
-			ret_8 = qq_process_get_buddies_online_reply(data, data_len, gc);
+			ret_8 = qq_process_get_buddies_online(data, data_len, gc);
 			if (ret_8  > 0 && ret_8 < 0xff) {
 				purple_debug_info("QQ", "Requesting for more online buddies\n");
 				qq_request_get_buddies_online(gc, ret_8, update_class);
 				return;
 			}
 			purple_debug_info("QQ", "All online buddies received\n");
-			qq_refresh_all_buddy_status(gc);
+			qq_update_buddyies_status(gc);
 			break;
 		case QQ_CMD_GET_LEVEL:
 			qq_process_get_level_reply(data, data_len, gc);
 			break;
 		case QQ_CMD_GET_BUDDIES_LIST:
-			ret_16 = qq_process_get_buddies_list_reply(data, data_len, gc);
+			ret_16 = qq_process_get_buddies(data, data_len, gc);
 			if (ret_16 > 0	&& ret_16 < 0xffff) {
 				purple_debug_info("QQ", "Requesting for more buddies\n");
-				qq_request_get_buddies_list(gc, ret_16, update_class);
+				qq_request_get_buddies(gc, ret_16, update_class);
 				return;
 			}
 			purple_debug_info("QQ", "All buddies received. Requesting buddies' levels\n");
@@ -610,8 +1080,23 @@
 			}
 			purple_debug_info("QQ", "All buddies and groups received\n");
 			break;
+		case QQ_CMD_AUTH_CODE:
+			qq_process_auth_code(gc, data, data_len, ship32);
+			break;
+		case QQ_CMD_BUDDY_QUESTION:
+			qq_process_question(gc, data, data_len, ship32);
+			break;
+		case QQ_CMD_ADD_BUDDY_NO_AUTH_EX:
+			qq_process_add_buddy_no_auth_ex(gc, data, data_len, ship32);
+			break;
+		case QQ_CMD_ADD_BUDDY_AUTH_EX:
+			qq_process_add_buddy_auth_ex(gc, data, data_len, ship32);
+			break;
+		case QQ_CMD_BUDDY_CHECK_CODE:
+			qq_process_buddy_check_code(gc, data, data_len);
+			break;
 		default:
-			process_cmd_unknow(gc, _("Unknow reply CMD"), data, data_len, cmd, seq);
+			process_unknow_cmd(gc, _("Unknow CLIENT CMD"), data, data_len, cmd, seq);
 			is_unknow = TRUE;
 			break;
 	}
--- a/libpurple/protocols/qq/qq_process.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/qq_process.h	Thu Nov 06 03:20:05 2008 +0000
@@ -34,13 +34,15 @@
 	QQ_CMD_CLASS_NONE = 0,
 	QQ_CMD_CLASS_UPDATE_ALL,
 	QQ_CMD_CLASS_UPDATE_ONLINE,
+	QQ_CMD_CLASS_UPDATE_BUDDY,
 	QQ_CMD_CLASS_UPDATE_ROOM,
 };
 
-void qq_proc_login_cmd(PurpleConnection *gc, guint8 *rcved, gint rcved_len);
-void qq_proc_client_cmd(PurpleConnection *gc, guint16 cmd, guint16 seq,
+guint8 qq_proc_login_cmds(PurpleConnection *gc,  guint16 cmd, guint16 seq,
 		guint8 *rcved, gint rcved_len, gint update_class, guint32 ship32);
-void qq_proc_room_cmd(PurpleConnection *gc, guint16 seq,
+void qq_proc_client_cmds(PurpleConnection *gc, guint16 cmd, guint16 seq,
+		guint8 *rcved, gint rcved_len, gint update_class, guint32 ship32);
+void qq_proc_room_cmds(PurpleConnection *gc, guint16 seq,
 		guint8 room_cmd, guint32 room_id, guint8 *rcved, gint rcved_len,
 		gint update_class, guint32 ship32);
 
@@ -49,5 +51,6 @@
 void qq_update_all(PurpleConnection *gc, guint16 cmd);
 void qq_update_online(PurpleConnection *gc, guint16 cmd);
 void qq_update_room(PurpleConnection *gc, guint8 room_cmd, guint32 room_id);
+void qq_update_all_rooms(PurpleConnection *gc, guint8 room_cmd, guint32 room_id);
 #endif
 
--- a/libpurple/protocols/qq/qq_trans.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/qq_trans.c	Thu Nov 06 03:20:05 2008 +0000
@@ -30,7 +30,7 @@
 #include "prefs.h"
 #include "request.h"
 
-#include "header_info.h"
+#include "qq_define.h"
 #include "qq_network.h"
 #include "qq_process.h"
 #include "qq_trans.h"
@@ -131,6 +131,7 @@
 	}
 
 	trans->update_class = update_class;
+	trans->ship32 = ship32;
 	return trans;
 }
 
--- a/libpurple/protocols/qq/send_file.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/send_file.c	Thu Nov 06 03:20:05 2008 +0000
@@ -31,7 +31,7 @@
 
 #include "buddy_list.h"
 #include "file_trans.h"
-#include "header_info.h"
+#include "qq_define.h"
 #include "im.h"
 #include "qq_base.h"
 #include "packet_parse.h"
@@ -54,8 +54,8 @@
 static int _qq_in_same_lan(ft_info *info)
 {
 	if (info->remote_internet_ip == info->local_internet_ip) return 1;
-	purple_debug_info("QQ", 
-			"Not in the same LAN, remote internet ip[%x], local internet ip[%x]\n",  
+	purple_debug_info("QQ",
+			"Not in the same LAN, remote internet ip[%x], local internet ip[%x]\n",
 			info->remote_internet_ip
 			, info->local_internet_ip);
 	return 0;
@@ -87,7 +87,7 @@
 	info = (ft_info *) xfer->data;
 	sinlen = sizeof(sin);
 	r = recvfrom(info->recv_fd, buf, len, 0, (struct sockaddr *) &sin, &sinlen);
-	purple_debug_info("QQ", 
+	purple_debug_info("QQ",
 			"==> recv %d bytes from File UDP Channel, remote ip[%s], remote port[%d]\n",
 			r, inet_ntoa(sin.sin_addr), g_ntohs(sin.sin_port));
 	return r;
@@ -121,11 +121,8 @@
 		sin.sin_port = g_htons(info->remote_minor_port);
 		sin.sin_addr.s_addr = g_htonl(info->remote_real_ip);
 	}
-	purple_debug_info("QQ", "sending to channel: %d.%d.%d.%d:%d\n",
-			(int)sin.sin_addr.s_addr & 0xff,
-			(int)(sin.sin_addr.s_addr >> 8) & 0xff,
-			(int)(sin.sin_addr.s_addr >> 16) & 0xff,
-			(int)sin.sin_addr.s_addr >> 24,
+	purple_debug_info("QQ", "sending to channel: %s:%d\n",
+			inet_ntoa(sin.sin_addr),
 			(int)g_ntohs(sin.sin_port)
 		  );
 	return sendto(info->sender_fd, buf, len, 0, (struct sockaddr *) &sin, sizeof(sin));
@@ -301,7 +298,7 @@
 	/* 004-007: sender uid */
 	bytes += qq_put32 (raw_data + bytes, to_uid);
 	/* 008-009: sender client version */
-	bytes += qq_put16 (raw_data + bytes, QQ_CLIENT);
+	bytes += qq_put16 (raw_data + bytes, qd->client_tag);
 	/* 010-013: receiver uid */
 	bytes += qq_put32 (raw_data + bytes, qd->uid);
 	/* 014-017: sender uid */
@@ -380,7 +377,7 @@
 
 static void _qq_xfer_init_socket(PurpleXfer *xfer)
 {
-	gint sockfd, listen_port = 0, i; 
+	gint sockfd, listen_port = 0, i;
 	socklen_t sin_len;
 	struct sockaddr_in sin;
 	ft_info *info;
@@ -389,7 +386,7 @@
 	g_return_if_fail(xfer->data != NULL);
 	info = (ft_info *) xfer->data;
 
-	/* debug 
+	/* debug
 	info->local_real_ip = 0x7f000001;
 	*/
 	info->local_real_ip = g_ntohl(inet_addr(purple_network_get_my_ip(-1)));
@@ -460,7 +457,7 @@
 	raw_data = g_newa(guint8, packet_len);
 	bytes = 0;
 
-	bytes += _qq_create_packet_file_header(raw_data + bytes, to_uid, 
+	bytes += _qq_create_packet_file_header(raw_data + bytes, to_uid,
 			QQ_FILE_TRANS_REQ, qd, FALSE);
 	bytes += qq_fill_conn_info(raw_data + bytes, info);
 	/* 079: 0x20 */
@@ -682,7 +679,7 @@
 }
 
 /* process reject im for file transfer request */
-void qq_process_recv_file_reject (guint8 *data, gint data_len, 
+void qq_process_recv_file_reject (guint8 *data, gint data_len,
 		guint32 sender_uid, PurpleConnection *gc)
 {
 	gchar *msg, *filename;
@@ -711,7 +708,7 @@
 }
 
 /* process cancel im for file transfer request */
-void qq_process_recv_file_cancel (guint8 *data, gint data_len, 
+void qq_process_recv_file_cancel (guint8 *data, gint data_len,
 		guint32 sender_uid, PurpleConnection *gc)
 {
 	gchar *msg, *filename;
@@ -774,7 +771,7 @@
 	gchar *sender_name, **fileinfo;
 	ft_info *info;
 	PurpleBuddy *b;
-	qq_buddy *q_bud;
+	qq_buddy_data *bd;
 	gint bytes;
 
 	g_return_if_fail (data != NULL && data_len != 0);
@@ -785,7 +782,7 @@
 	info->local_internet_port = qd->my_port;
 	info->local_real_ip = 0x00000000;
 	info->to_uid = sender_uid;
-	
+
 	if (data_len <= 2 + 30 + QQ_CONN_INFO_LEN) {
 		purple_debug_warning("QQ", "Received file request message is empty\n");
 		return;
@@ -804,36 +801,37 @@
 	/* FACE from IP detector, ignored by gfhuang */
 	if(g_ascii_strcasecmp(fileinfo[0], "FACE") == 0) {
 		purple_debug_warning("QQ",
-			    "Received a FACE ip detect from qq-%d, so he/she must be online :)\n", sender_uid);
+			    "Received a FACE ip detect from %d, so he/she must be online :)\n", sender_uid);
 
 		b = purple_find_buddy(gc->account, sender_name);
-		q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
-		if (q_bud) {
+		bd = (b == NULL) ? NULL : (qq_buddy_data *) b->proto_data;
+		if (bd) {
 			if(0 != info->remote_real_ip) {
-				g_memmove(&(q_bud->ip), &info->remote_real_ip, sizeof(q_bud->ip));
-				q_bud->port = info->remote_minor_port;
+				g_memmove(&(bd->ip), &info->remote_real_ip, sizeof(bd->ip));
+				bd->port = info->remote_minor_port;
 			}
 			else if (0 != info->remote_internet_ip) {
-				g_memmove(&(q_bud->ip), &info->remote_internet_ip, sizeof(q_bud->ip));
-				q_bud->port = info->remote_major_port;
+				g_memmove(&(bd->ip), &info->remote_internet_ip, sizeof(bd->ip));
+				bd->port = info->remote_major_port;
 			}
 
-			if(!is_online(q_bud->status)) {
-				q_bud->status = QQ_BUDDY_ONLINE_INVISIBLE;
-				qq_update_buddy_contact(gc, q_bud);
+			if(!is_online(bd->status)) {
+				bd->status = QQ_BUDDY_ONLINE_INVISIBLE;
+				bd->last_update = time(NULL);
+				qq_update_buddy_status(gc, bd->uid, bd->status, bd->comm_flag);
 			}
-			else 
+			else
 				purple_debug_info("QQ", "buddy %d is already online\n", sender_uid);
 
 		}
-		else 
+		else
 			purple_debug_warning("QQ", "buddy %d is not in list\n", sender_uid);
 
-		g_free(sender_name);	    
+		g_free(sender_name);
 		g_strfreev(fileinfo);
 		return;
 	}
-	
+
 	xfer = purple_xfer_new(purple_connection_get_account(gc),
 			PURPLE_XFER_RECEIVE,
 			sender_name);
@@ -875,7 +873,7 @@
 	*/
 }
 
-void qq_process_recv_file_notify(guint8 *data, gint data_len, 
+void qq_process_recv_file_notify(guint8 *data, gint data_len,
 		guint32 sender_uid, PurpleConnection *gc)
 {
 	gint bytes;
@@ -892,7 +890,7 @@
 		purple_debug_warning("QQ", "Received file notify message is empty\n");
 		return;
 	}
-	
+
 	bytes = 0;
 	bytes += qq_get16(&(info->send_seq), data + bytes);
 
--- a/libpurple/protocols/qq/sys_msg.c	Thu Nov 06 02:21:16 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,357 +0,0 @@
-/**
- * @file sys_msg.c
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#include "debug.h"
-#include "internal.h"
-#include "notify.h"
-#include "request.h"
-
-#include "buddy_info.h"
-#include "buddy_list.h"
-#include "buddy_opt.h"
-#include "char_conv.h"
-#include "header_info.h"
-#include "packet_parse.h"
-#include "qq.h"
-#include "qq_network.h"
-#include "sys_msg.h"
-#include "utils.h"
-
-enum {
-	QQ_MSG_SYS_BEING_ADDED = 0x01,
-	QQ_MSG_SYS_ADD_CONTACT_REQUEST = 0x02,
-	QQ_MSG_SYS_ADD_CONTACT_APPROVED = 0x03,
-	QQ_MSG_SYS_ADD_CONTACT_REJECTED = 0x04,
-	QQ_MSG_SYS_NOTICE= 0x06,
-	QQ_MSG_SYS_NEW_VERSION = 0x09
-};
-
-/* Henry: private function for reading/writing of system log */
-static void _qq_sys_msg_log_write(PurpleConnection *gc, gchar *msg, gchar *from)
-{
-	PurpleLog *log;
-	PurpleAccount *account;
-
-	account = purple_connection_get_account(gc);
-
-	log = purple_log_new(PURPLE_LOG_IM,
-			"systemim",
-			account,
-			NULL,
-			time(NULL),
-			NULL
-			);
-	purple_log_write(log, PURPLE_MESSAGE_SYSTEM, from,
-			time(NULL), msg);
-	purple_log_free(log);
-}
-
-/* suggested by rakescar@linuxsir, can still approve after search */
-static void _qq_search_before_auth_with_gc_and_uid(gc_and_uid *g)
-{
-	PurpleConnection *gc;
-	guint32 uid;
-	gchar *nombre;
-
-	g_return_if_fail(g != NULL);
-
-	gc = g->gc;
-	uid = g->uid;
-	g_return_if_fail(gc != 0 && uid != 0);
-
-	qq_send_packet_get_info(gc, uid, TRUE);	/* we want to see window */
-
-	nombre = uid_to_purple_name(uid);
-	purple_request_action
-	    (gc, NULL, _("Do you approve the requestion?"), "",
-		PURPLE_DEFAULT_ACTION_NONE,
-		 purple_connection_get_account(gc), nombre, NULL,
-		 g, 2,
-	     _("Reject"), G_CALLBACK(qq_reject_add_request_with_gc_and_uid),
-	     _("Approve"), G_CALLBACK(qq_approve_add_request_with_gc_and_uid));
-	g_free(nombre);
-}
-
-static void _qq_search_before_add_with_gc_and_uid(gc_and_uid *g)
-{
-	PurpleConnection *gc;
-	guint32 uid;
-	gchar *nombre;
-
-	g_return_if_fail(g != NULL);
-
-	gc = g->gc;
-	uid = g->uid;
-	g_return_if_fail(gc != 0 && uid != 0);
-
-	qq_send_packet_get_info(gc, uid, TRUE);	/* we want to see window */
-	nombre = uid_to_purple_name(uid);
-	purple_request_action
-	    (gc, NULL, _("Do you add the buddy?"), "",
-		PURPLE_DEFAULT_ACTION_NONE,
-		 purple_connection_get_account(gc), nombre, NULL,
-		 g, 2,
-	     _("Cancel"), NULL,
-		 _("Add"), G_CALLBACK(qq_add_buddy_with_gc_and_uid));
-	g_free(nombre);
-}
-
-/* Send ACK if the sys message needs an ACK */
-static void _qq_send_packet_ack_msg_sys(PurpleConnection *gc, guint8 code, guint32 from, guint16 seq)
-{
-	qq_data *qd;
-	guint8 bar, *ack;
-	gchar *str;
-	gint ack_len, bytes;
-
-	qd = (qq_data *) gc->proto_data;
-
-	str = g_strdup_printf("%d", from);
-	bar = 0x1e;
-	ack_len = 1 + 1 + strlen(str) + 1 + 2;
-	ack = g_newa(guint8, ack_len);
-
-	bytes = 0;
-	bytes += qq_put8(ack + bytes, code);
-	bytes += qq_put8(ack + bytes, bar);
-	bytes += qq_putdata(ack + bytes, (guint8 *) str, strlen(str));
-	bytes += qq_put8(ack + bytes, bar);
-	bytes += qq_put16(ack + bytes, seq);
-
-	g_free(str);
-
-	if (bytes == ack_len)	/* creation OK */
-		qq_send_server_reply(gc, QQ_CMD_ACK_SYS_MSG, 0, ack, ack_len);
-	else
-		purple_debug_error("QQ",
-			   "Fail creating sys msg ACK, expect %d bytes, build %d bytes\n", ack_len, bytes);
-}
-
-/* when you are added by a person, QQ server will send sys message */
-static void _qq_process_msg_sys_being_added(PurpleConnection *gc, gchar *from, gchar *to, gchar *msg_utf8)
-{
-	gchar *message;
-	PurpleBuddy *b;
-	guint32 uid;
-	gc_and_uid *g;
-	gchar *name;
-
-	g_return_if_fail(from != NULL && to != NULL);
-
-	uid = strtol(from, NULL, 10);
-	name = uid_to_purple_name(uid);
-	b = purple_find_buddy(gc->account, name);
-
-	if (b == NULL) {	/* the person is not in my list */
-		g = g_new0(gc_and_uid, 1);
-		g->gc = gc;
-		g->uid = uid;	/* only need to get value */
-		message = g_strdup_printf(_("You have been added by %s"), from);
-		_qq_sys_msg_log_write(gc, message, from);
-		purple_request_action(gc, NULL, message,
-				    _("Would you like to add him?"),
-					PURPLE_DEFAULT_ACTION_NONE,
-					purple_connection_get_account(gc), name, NULL,
-					g, 3,
-				    _("Cancel"), NULL,
-					_("Add"), G_CALLBACK(qq_add_buddy_with_gc_and_uid),
-				    _("Search"), G_CALLBACK(_qq_search_before_add_with_gc_and_uid));
-	} else {
-		message = g_strdup_printf(_("%s added you [%s] to buddy list"), from, to);
-		_qq_sys_msg_log_write(gc, message, from);
-		purple_notify_info(gc, _("QQ Budy"), _("Successed:"), message);
-	}
-
-	g_free(name);
-	g_free(message);
-}
-
-/* you are rejected by the person */
-static void _qq_process_msg_sys_add_contact_rejected(PurpleConnection *gc, gchar *from, gchar *to, gchar *msg_utf8)
-{
-	gchar *message, *reason;
-
-	g_return_if_fail(from != NULL && to != NULL);
-
-	message = g_strdup_printf(_("Requestion rejected by %s"), from);
-	reason = g_strdup_printf(_("Message: %s"), msg_utf8);
-	_qq_sys_msg_log_write(gc, message, from);
-
-	purple_notify_info(gc, _("QQ Buddy"), message, reason);
-	g_free(message);
-	g_free(reason);
-}
-
-/* the buddy approves your request of adding him/her as your friend */
-static void _qq_process_msg_sys_add_contact_approved(PurpleConnection *gc, gchar *from, gchar *to, gchar *msg_utf8)
-{
-	gchar *message;
-	qq_data *qd;
-
-	g_return_if_fail(from != NULL && to != NULL);
-
-	qd = (qq_data *) gc->proto_data;
-	qq_add_buddy_by_recv_packet(gc, strtol(from, NULL, 10), TRUE, TRUE);
-
-	message = g_strdup_printf(_("Requestion approved by %s"), from);
-	_qq_sys_msg_log_write(gc, message, from);
-	purple_notify_info(gc, _("QQ Buddy"), _("Notice:"), message);
-
-	g_free(message);
-}
-
-/* someone wants to add you to his buddy list */
-static void _qq_process_msg_sys_add_contact_request(PurpleConnection *gc, gchar *from, gchar *to, gchar *msg_utf8)
-{
-	gchar *message, *reason;
-	guint32 uid;
-	gc_and_uid *g, *g2;
-	PurpleBuddy *b;
-	gchar *name;
-
-	g_return_if_fail(from != NULL && to != NULL);
-
-	uid = strtol(from, NULL, 10);
-	g = g_new0(gc_and_uid, 1);
-	g->gc = gc;
-	g->uid = uid;
-
-	name = uid_to_purple_name(uid);
-
-	/* TODO: this should go through purple_account_request_authorization() */
-	message = g_strdup_printf(_("%s wants to add you [%s] as a friend"), from, to);
-	reason = g_strdup_printf(_("Message: %s"), msg_utf8);
-	_qq_sys_msg_log_write(gc, message, from);
-
-	purple_request_action
-	    (gc, NULL, message, reason, PURPLE_DEFAULT_ACTION_NONE,
-		purple_connection_get_account(gc), name, NULL,
-		 g, 3,
-	     _("Reject"),
-	     G_CALLBACK(qq_reject_add_request_with_gc_and_uid),
-	     _("Approve"),
-	     G_CALLBACK(qq_approve_add_request_with_gc_and_uid),
-	     _("Search"), G_CALLBACK(_qq_search_before_auth_with_gc_and_uid));
-
-	g_free(message);
-	g_free(reason);
-
-	/* XXX: Is this needed once the above goes through purple_account_request_authorization()? */
-	b = purple_find_buddy(gc->account, name);
-	if (b == NULL) {	/* the person is not in my list  */
-		g2 = g_new0(gc_and_uid, 1);
-		g2->gc = gc;
-		g2->uid = strtol(from, NULL, 10);
-		message = g_strdup_printf(_("%s is not in buddy list"), from);
-		purple_request_action(gc, NULL, message,
-				    _("Would you add?"), PURPLE_DEFAULT_ACTION_NONE,
-					purple_connection_get_account(gc), name, NULL,
-					g2, 3,
-					_("Cancel"), NULL,
-					_("Add"), G_CALLBACK(qq_add_buddy_with_gc_and_uid),
-				    _("Search"), G_CALLBACK(_qq_search_before_add_with_gc_and_uid));
-		g_free(message);
-	}
-
-	g_free(name);
-}
-
-static void _qq_process_msg_sys_notice(PurpleConnection *gc, gchar *from, gchar *to, gchar *msg_utf8)
-{
-	qq_data *qd = (qq_data *) gc->proto_data;
-	gchar *title, *content;
-
-	g_return_if_fail(from != NULL && to != NULL);
-
-	title = g_strdup_printf(_("From %s:"), from);
-	content = g_strdup_printf(_("%s"), msg_utf8);
-
-	if (qd->is_show_notice) {
-		purple_notify_info(gc, _("QQ Server Notice"), title, content);
-	} else {
-		purple_debug_info("QQ", "QQ Server notice from %s:\n%s", from, msg_utf8);
-}
-	g_free(title);
-	g_free(content);
-}
-
-void qq_process_msg_sys(guint8 *data, gint data_len, guint16 seq, PurpleConnection *gc)
-{
-	qq_data *qd;
-	gchar **segments, *code, *from, *to, *msg, *msg_utf8;
-
-	g_return_if_fail(data != NULL && data_len != 0);
-
-	qd = (qq_data *) gc->proto_data;
-
-	if (NULL == (segments = split_data(data, data_len, "\x1f", 4)))
-		return;
-	code = segments[0];
-	from = segments[1];
-	to = segments[2];
-	msg = segments[3];
-
-	_qq_send_packet_ack_msg_sys(gc, code[0], strtol(from, NULL, 10), seq);
-
-	if (strtol(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;
-	}
-
-	msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT);
-	if (from == NULL && msg_utf8) {
-		purple_debug_error("QQ", "Recv NULL sys msg to [%s], discard\n", to);
-		g_strfreev(segments);
-		g_free(msg_utf8);
-		return;
-	}
-
-	switch (strtol(code, NULL, 10)) {
-	case QQ_MSG_SYS_BEING_ADDED:
-		_qq_process_msg_sys_being_added(gc, from, to, msg_utf8);
-		break;
-	case QQ_MSG_SYS_ADD_CONTACT_REQUEST:
-		_qq_process_msg_sys_add_contact_request(gc, from, to, msg_utf8);
-		break;
-	case QQ_MSG_SYS_ADD_CONTACT_APPROVED:
-		_qq_process_msg_sys_add_contact_approved(gc, from, to, msg_utf8);
-		break;
-	case QQ_MSG_SYS_ADD_CONTACT_REJECTED:
-		_qq_process_msg_sys_add_contact_rejected(gc, from, to, msg_utf8);
-		break;
-	case QQ_MSG_SYS_NOTICE:
-		_qq_process_msg_sys_notice(gc, from, to, msg_utf8);
-		break;
-	case QQ_MSG_SYS_NEW_VERSION:
-		purple_debug_warning("QQ",
-			   "QQ server says there is newer version than %s\n", qq_get_ver_desc(QQ_CLIENT));
-		break;
-	default:
-		purple_debug_warning("QQ", "Recv unknown sys msg code: %s\n", code);
-		purple_debug_warning("QQ", "the msg is : %s\n", msg_utf8);
-	}
-	g_free(msg_utf8);
-	g_strfreev(segments);
-}
--- a/libpurple/protocols/qq/sys_msg.h	Thu Nov 06 02:21:16 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-/**
- * @file sys_msg.h
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#ifndef _QQ_SYS_MSG_H_
-#define _QQ_SYS_MSG_H_
-
-#include <glib.h>
-#include "connection.h"
-
-void qq_process_msg_sys(guint8 *data, gint data_len, guint16 seq, PurpleConnection *gc);
-
-#endif
--- a/libpurple/protocols/qq/utils.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/utils.c	Thu Nov 06 03:20:05 2008 +0000
@@ -91,12 +91,6 @@
 	return g_strdup_printf("%d", index);
 }
 
-gint qq_string_to_dec_value(const gchar *str)
-{
-	g_return_val_if_fail(str != NULL, 0);
-	return strtol(str, NULL, 10);
-}
-
 /* split the given data(len) with delimit,
  * check the number of field matches the expected_fields (<=0 means all)
  * return gchar* array (needs to be freed by g_strfreev later), or NULL */
@@ -104,7 +98,7 @@
 {
 	guint8 *input;
 	gchar **segments;
-	gint i, j;
+	gint count, j;
 
 	g_return_val_if_fail(data != NULL && len != 0 && delimit != 0, NULL);
 
@@ -118,22 +112,17 @@
 	if (expected_fields <= 0)
 		return segments;
 
-	for (i = 0; segments[i] != NULL; i++) {;
-	}
-	if (i < expected_fields) {	/* not enough fields */
-		purple_debug_error("QQ", "Invalid data, expect %d fields, found only %d, discard\n",
-				expected_fields, i);
-		g_strfreev(segments);
+	count = g_strv_length(segments);
+	if (count < expected_fields) {	/* not enough fields */
+		purple_debug_error("QQ", "Less fields %d then %d\n", count, expected_fields);
 		return NULL;
-	} else if (i > expected_fields) {	/* more fields, OK */
-		purple_debug_warning("QQ", "Dangerous data, expect %d fields, found %d, return all\n",
-				expected_fields, i);
+	} else if (count > expected_fields) {	/* more fields, OK */
+		purple_debug_warning("QQ", "More fields %d than %d\n", count, expected_fields);
 		/* free up those not used */
-		for (j = expected_fields; j < i; j++) {
+		for (j = expected_fields; j < count; j++) {
 			purple_debug_warning("QQ", "field[%d] is %s\n", j, segments[j]);
 			g_free(segments[j]);
 		}
-
 		segments[expected_fields] = NULL;
 	}
 
@@ -183,20 +172,6 @@
 	return g_strdup_printf(QQ_NAME_FORMAT, uid);
 }
 
-/* convert name displayed in a chat channel to original QQ UID */
-gchar *chat_name_to_purple_name(const gchar *const name)
-{
-	const gchar *tmp;
-	gchar *ret;
-
-	g_return_val_if_fail(name != NULL, NULL);
-
-	tmp = (gchar *) purple_strcasestr(name, "(qq-");
-	ret = g_strndup(tmp + 4, strlen(name) - (tmp - name) - 4 - 1);
-
-	return ret;
-}
-
 /* try to dump the data as GBK */
 gchar* try_dump_as_gbk(const guint8 *const data, gint len)
 {
@@ -335,7 +310,7 @@
 }
 
 void qq_hex_dump(PurpleDebugLevel level, const char *category,
-		const guint8 *pdata, gint bytes,	
+		const guint8 *pdata, gint bytes,
 		const char *format, ...)
 {
 	va_list args;
@@ -361,25 +336,6 @@
 
 void qq_show_packet(const gchar *desc, const guint8 *buf, gint len)
 {
-	qq_hex_dump(PURPLE_DEBUG_INFO, "QQ", buf, len, desc);
+	qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ", buf, len, desc);
 }
 
-/* convert face num from packet (0-299) to local face (1-100) */
-gchar *face_to_icon_str(gint face)
-{
-	gchar *icon_num_str;
-	gint icon_num = face / 3 + 1;
-	icon_num_str = g_strdup_printf("%d", icon_num);
-	return icon_num_str;
-}
-
-/* return the location of the buddy icon dir
- * any application using libpurple but not installing the QQ buddy icons
- * under datadir needs to set the pref below, or buddy icons won't work */
-const char *qq_buddy_icon_dir(void)
-{
-	if (purple_prefs_exists("/prpl/qq/buddy_icon_dir"))
-		return purple_prefs_get_string("/prpl/qq/buddy_icon_dir");
-	else
-		return NULL;
-}
--- a/libpurple/protocols/qq/utils.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/protocols/qq/utils.h	Thu Nov 06 03:20:05 2008 +0000
@@ -34,7 +34,6 @@
 
 gchar *get_name_by_index_str(gchar **array, const gchar *index_str, gint amount);
 gchar *get_index_str_by_name(gchar **array, const gchar *name, gint amount);
-gint qq_string_to_dec_value(const gchar *str);
 
 gchar **split_data(guint8 *data, gint len, const gchar *delimit, gint expected_fields);
 
@@ -43,18 +42,13 @@
 
 guint32 purple_name_to_uid(const gchar *name);
 gchar *uid_to_purple_name(guint32 uid);
-gchar *chat_name_to_purple_name(const gchar *const name);
-
-gchar *face_to_icon_str(gint face);
 
 gchar *try_dump_as_gbk(const guint8 *const data, gint len);
 
 void qq_show_packet(const gchar *desc, const guint8 *buf, gint len);
 void qq_hex_dump(PurpleDebugLevel level, const char *category,
-		const guint8 *pdata, gint bytes,	
+		const guint8 *pdata, gint bytes,
 		const char *format, ...);
 guint8 *hex_str_to_bytes(const gchar *buf, gint *out_len);
 
-const gchar *qq_buddy_icon_dir(void);
-
 #endif
--- a/libpurple/proxy.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/proxy.c	Thu Nov 06 03:20:05 2008 +0000
@@ -2152,6 +2152,8 @@
 			break;
 
 		default:
+			purple_debug_error("proxy", "Invalid Proxy type (%d) specified.\n",
+							   purple_proxy_info_get_type(connect_data->gpi));
 			purple_proxy_connect_data_destroy(connect_data);
 			return NULL;
 	}
@@ -2160,6 +2162,7 @@
 			connectport, connection_host_resolved, connect_data);
 	if (connect_data->query_data == NULL)
 	{
+		purple_debug_error("proxy", "dns query failed unexpectedly.\n");
 		purple_proxy_connect_data_destroy(connect_data);
 		return NULL;
 	}
--- a/libpurple/prpl.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/prpl.c	Thu Nov 06 03:20:05 2008 +0000
@@ -23,6 +23,7 @@
 #include "internal.h"
 #include "conversation.h"
 #include "debug.h"
+#include "network.h"
 #include "notify.h"
 #include "prpl.h"
 #include "request.h"
@@ -317,7 +318,8 @@
 	PurplePluginProtocolInfo *prpl_info;
 
 	if (purple_status_is_online(new_status) &&
-		purple_account_is_disconnected(account))
+		purple_account_is_disconnected(account) &&
+		purple_network_is_available())
 	{
 		purple_account_connect(account);
 		return;
--- a/libpurple/server.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/server.c	Thu Nov 06 03:20:05 2008 +0000
@@ -264,14 +264,18 @@
 		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, b->name, account);
 		if(conv != NULL && alias != NULL && strcmp(alias, who))
 		{
+			char *escaped = g_markup_escape_text(who, -1);
+			char *escaped2 = g_markup_escape_text(alias, -1);
 			char *tmp = g_strdup_printf(_("%s is now known as %s.\n"),
-										who, alias);
+										escaped, escaped2);
 
 			purple_conversation_write(conv, NULL, tmp,
 					PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY,
 					time(NULL));
 
 			g_free(tmp);
+			g_free(escaped2);
+			g_free(escaped);
 		}
 	}
 }
--- a/libpurple/sslconn.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/sslconn.c	Thu Nov 06 03:20:05 2008 +0000
@@ -23,6 +23,8 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+#define _PURPLE_SSLCONN_C_
+
 #include "internal.h"
 
 #include "certificate.h"
--- a/libpurple/sslconn.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/sslconn.h	Thu Nov 06 03:20:05 2008 +0000
@@ -185,7 +185,7 @@
 									PurpleSslErrorFunction error_func,
 									void *data);
 
-#ifndef PURPLE_DISABLE_DEPRECATED
+#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_SSLCONN_C_)
 /**
  * Makes a SSL connection using an already open file descriptor.
  *
--- a/libpurple/util.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/libpurple/util.c	Thu Nov 06 03:20:05 2008 +0000
@@ -3979,6 +3979,13 @@
 					     callback, user_data);
 }
 
+static gboolean
+url_fetch_connect_failed(gpointer data)
+{
+	url_fetch_connect_cb(data, -1, "");
+	return FALSE;
+}
+
 PurpleUtilFetchUrlData *
 purple_util_fetch_url_request_len(const char *url, gboolean full,
 		const char *user_agent, gboolean http11,
@@ -4016,9 +4023,8 @@
 
 	if (gfud->connect_data == NULL)
 	{
-		purple_util_fetch_url_error(gfud, _("Unable to connect to %s"),
-				gfud->website.address);
-		return NULL;
+		/* Trigger the connect_cb asynchronously. */
+		purple_timeout_add(10, url_fetch_connect_failed, gfud);
 	}
 
 	return gfud;
--- a/pidgin.spec.in	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin.spec.in	Thu Nov 06 03:20:05 2008 +0000
@@ -263,6 +263,7 @@
 rm -f $RPM_BUILD_ROOT%{_libdir}/purple-2/libjabber.so
 rm -f $RPM_BUILD_ROOT%{_libdir}/*.la
 rm -f $RPM_BUILD_ROOT%{perl_archlib}/perllocal.pod
+find $RPM_BUILD_ROOT -type f -name '*.a' -exec rm -f {} ';'
 find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';'
 find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';'
 
--- a/pidgin/Makefile.am	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -190,6 +190,7 @@
 pidgin_LDFLAGS = -export-dynamic
 pidgin_LDADD = \
 	@LIBOBJS@ \
+	$(GLIB_LIBS) \
 	$(DBUS_LIBS) \
 	$(GSTREAMER_LIBS) \
 	$(XSS_LIBS) \
--- a/pidgin/gtkaccount.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/gtkaccount.c	Thu Nov 06 03:20:05 2008 +0000
@@ -562,7 +562,8 @@
 
 	/* Set the fields. */
 	if (dialog->account != NULL) {
-		if (purple_account_get_password(dialog->account))
+		if (purple_account_get_password(dialog->account) &&
+		    purple_account_get_remember_password(dialog->account))
 			gtk_entry_set_text(GTK_ENTRY(dialog->password_entry),
 							   purple_account_get_password(dialog->account));
 
--- a/pidgin/gtkblist.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/gtkblist.c	Thu Nov 06 03:20:05 2008 +0000
@@ -6951,7 +6951,7 @@
 	pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Group:"), data->sg, data->group_combo, TRUE, NULL);
 	
 	data->autojoin = gtk_check_button_new_with_mnemonic(_("Auto_join when account becomes online."));
-	data->persistent = gtk_check_button_new_with_mnemonic(_("_Hide chat when the window is closed."));
+	data->persistent = gtk_check_button_new_with_mnemonic(_("_Remain in chat after window is closed."));
 	gtk_box_pack_start(GTK_BOX(vbox), data->autojoin, FALSE, FALSE, 0);
 	gtk_box_pack_start(GTK_BOX(vbox), data->persistent, FALSE, FALSE, 0);
 
@@ -7177,7 +7177,10 @@
 	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/y", 0);
 	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/width", 250); /* Golden ratio, baby */
 	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/height", 405); /* Golden ratio, baby */
+#if !GTK_CHECK_VERSION(2,14,0)
+	/* This pref is used in pidgintooltip.c. */
 	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay", 500);
+#endif
 
 	/* Register our signals */
 	purple_signal_register(gtk_blist_handle, "gtkblist-hiding",
--- a/pidgin/gtkconn.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/gtkconn.c	Thu Nov 06 03:20:05 2008 +0000
@@ -208,9 +208,10 @@
 	while (l) {
 		PurpleAccount *a = (PurpleAccount*)l->data;
 		if (!purple_account_is_disconnected(a)) {
-			purple_connection_error_reason(purple_account_get_connection(a),
-			                               PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-			                               _("Network disconnected"));
+			char *password = g_strdup(purple_account_get_password(a));
+			purple_account_disconnect(a);
+			purple_account_set_password(a, password);
+			g_free(password);
 		}
 		l = l->next;
 	}
--- a/pidgin/gtkconv.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/gtkconv.c	Thu Nov 06 03:20:05 2008 +0000
@@ -24,6 +24,8 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
+#define _PIDGIN_GTKCONV_C_
+
 #include "internal.h"
 #include "pidgin.h"
 
@@ -163,7 +165,7 @@
 gboolean pidgin_conv_has_focus(PurpleConversation *conv);
 static GdkColor* generate_nick_colors(guint *numcolors, GdkColor background);
 static gboolean color_is_visible(GdkColor foreground, GdkColor background, int color_contrast, int brightness_contrast);
-static GtkTextTag *get_buddy_tag(PurpleConversation *conv, const char *who, gboolean create);
+static GtkTextTag *get_buddy_tag(PurpleConversation *conv, const char *who, PurpleMessageFlags flag, gboolean create);
 static void pidgin_conv_update_fields(PurpleConversation *conv, PidginConvFields fields);
 static void focus_out_from_menubar(GtkWidget *wid, PidginWindow *win);
 static void pidgin_conv_tab_pack(PidginWindow *win, PidginConversation *gtkconv);
@@ -174,7 +176,8 @@
 		int width, int height);
 static gboolean pidgin_conv_xy_to_right_infopane(PidginWindow *win, int x, int y);
 
-static const GdkColor *get_nick_color(PidginConversation *gtkconv, const char *name) {
+static const GdkColor *get_nick_color(PidginConversation *gtkconv, const char *name)
+{
 	static GdkColor col;
 	GtkStyle *style = gtk_widget_get_style(gtkconv->imhtml);
 	float scale;
@@ -1924,6 +1927,40 @@
 }
 
 static gboolean
+gtkconv_cycle_focus(PidginConversation *gtkconv, GtkDirectionType dir)
+{
+	PurpleConversation *conv = gtkconv->active_conv;
+	gboolean chat = purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT;
+	GtkWidget *next = NULL;
+	struct {
+		GtkWidget *from;
+		GtkWidget *to;
+	} transitions[] = {
+		{gtkconv->entry, gtkconv->imhtml},
+		{gtkconv->imhtml, chat ? gtkconv->u.chat->list : gtkconv->entry},
+		{chat ? gtkconv->u.chat->list : NULL, gtkconv->entry},
+		{NULL, NULL}
+	}, *ptr;
+
+	for (ptr = transitions; !next && ptr->from; ptr++) {
+		GtkWidget *from, *to;
+		if (dir == GTK_DIR_TAB_FORWARD) {
+			from = ptr->from;
+			to = ptr->to;
+		} else {
+			from = ptr->to;
+			to = ptr->from;
+		}
+		if (gtk_widget_is_focus(from))
+			next = to;
+	}
+
+	if (next)
+		gtk_widget_grab_focus(next);
+	return !!next;
+}
+
+static gboolean
 conv_keypress_common(PidginConversation *gtkconv, GdkEventKey *event)
 {
 	PidginWindow *win;
@@ -1984,7 +2021,10 @@
 #endif
 				return TRUE;
 				break;
-
+			case GDK_F6:
+				if (gtkconv_cycle_focus(gtkconv, event->state & GDK_SHIFT_MASK ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD))
+					return TRUE;
+				break;
 		} /* End of switch */
 	}
 
@@ -2011,6 +2051,10 @@
 				return TRUE;
 			}
 			break;
+		case GDK_F6:
+			if (gtkconv_cycle_focus(gtkconv, event->state & GDK_SHIFT_MASK ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD))
+				return TRUE;
+			break;
 		}
 	}
 	return FALSE;
@@ -4460,7 +4504,7 @@
 
 	blist_node_aliased_cb((PurpleBlistNode *)buddy, NULL, conv);
 
-	texttag = get_buddy_tag(conv, purple_buddy_get_name(buddy), FALSE); /* XXX: do we want the normalized name? */
+	texttag = get_buddy_tag(conv, purple_buddy_get_name(buddy), 0, FALSE); /* XXX: do we want the normalized name? */
 	if (texttag) {
 		g_object_set(texttag, "weight", is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL, NULL);
 	}
@@ -5378,16 +5422,35 @@
 	purple_conversation_write(conv, who, message, flags, mtime);
 }
 
+static const char *
+get_text_tag_color(GtkTextTag *tag)
+{
+	GdkColor *color = NULL;
+	gboolean set = FALSE;
+	static char colcode[] = "#XXXXXX";
+	if (tag)
+		g_object_get(G_OBJECT(tag), "foreground-set", &set, "foreground-gdk", &color, NULL);
+	if (set && color)
+		g_snprintf(colcode, sizeof(colcode), "#%02x%02x%02x",
+				color->red >> 8, color->green >> 8, color->blue >> 8);
+	else
+		colcode[0] = '\0';
+	if (color)
+		gdk_color_free(color);
+	return colcode;
+}
+
 /* The callback for an event on a link tag. */
 static gboolean buddytag_event(GtkTextTag *tag, GObject *imhtml,
-		GdkEvent *event, GtkTextIter *arg2, gpointer data) {
+		GdkEvent *event, GtkTextIter *arg2, gpointer data)
+{
 	if (event->type == GDK_BUTTON_PRESS
 			|| event->type == GDK_2BUTTON_PRESS) {
 		GdkEventButton *btn_event = (GdkEventButton*) event;
 		PurpleConversation *conv = data;
 		char *buddyname;
 
-		/* strlen("BUDDY ") == 6 */
+		/* strlen("BUDDY " or "HILIT ") == 6 */
 		g_return_val_if_fail((tag->name != NULL)
 				&& (strlen(tag->name) > 6), FALSE);
 
@@ -5427,24 +5490,33 @@
 	return FALSE;
 }
 
-static GtkTextTag *get_buddy_tag(PurpleConversation *conv, const char *who, gboolean create)
+static GtkTextTag *get_buddy_tag(PurpleConversation *conv, const char *who, PurpleMessageFlags flag,
+		gboolean create)
 {
 	PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
 	GtkTextTag *buddytag;
 	gchar *str;
-
-	str = g_strdup_printf("BUDDY %s", who);
+	gboolean highlight = (flag & PURPLE_MESSAGE_NICK);
+	GtkTextBuffer *buffer = GTK_IMHTML(gtkconv->imhtml)->text_buffer;
+
+	str = g_strdup_printf(highlight ? "HILIT %s" : "BUDDY %s", who);
 
 	buddytag = gtk_text_tag_table_lookup(
-			gtk_text_buffer_get_tag_table(
-				GTK_IMHTML(gtkconv->imhtml)->text_buffer), str);
+			gtk_text_buffer_get_tag_table(buffer), str);
 
 	if (buddytag == NULL && create) {
-		buddytag = gtk_text_buffer_create_tag(
-				GTK_IMHTML(gtkconv->imhtml)->text_buffer, str,
-				"foreground-gdk", get_nick_color(gtkconv, who),
-				"weight", purple_find_buddy(purple_conversation_get_account(conv), who) ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
-				NULL);
+		if (highlight)
+			buddytag = gtk_text_buffer_create_tag(buffer, str,
+					"foreground", get_text_tag_color(gtk_text_tag_table_lookup(
+							gtk_text_buffer_get_tag_table(buffer), "highlight-name")),
+					"weight", PANGO_WEIGHT_BOLD,
+					NULL);
+		else
+			buddytag = gtk_text_buffer_create_tag(
+					buffer, str,
+					"foreground-gdk", get_nick_color(gtkconv, who),
+					"weight", purple_find_buddy(purple_conversation_get_account(conv), who) ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
+					NULL);
 
 		g_signal_connect(G_OBJECT(buddytag), "event",
 				G_CALLBACK(buddytag_event), conv);
@@ -5801,7 +5873,9 @@
 				}
 
 				if (flags & PURPLE_MESSAGE_NICK) {
-					tagname = "highlight-name";
+					if (type == PURPLE_CONV_TYPE_IM) {
+						tagname = "highlight-name";
+					}
 				} else if (flags & PURPLE_MESSAGE_RECV) {
 					/* The tagname for chats is handled by get_buddy_tag */
 					if (type == PURPLE_CONV_TYPE_IM) {
@@ -5820,7 +5894,7 @@
 		if (tagname)
 			tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), tagname);
 		else
-			tag = get_buddy_tag(conv, name, TRUE);
+			tag = get_buddy_tag(conv, name, flags, TRUE);
 
 		if (GTK_IMHTML(gtkconv->imhtml)->show_comments) {
 			/* The color for the timestamp has to be set in the font-tags, unfortunately.
@@ -5828,19 +5902,10 @@
 			 * bold. I thought applying the "comment" tag again, which has "weight" set
 			 * to PANGO_WEIGHT_NORMAL, would remove the boldness. But it doesn't. So
 			 * this will have to do. I don't terribly like it.  -- sadrul */
-			GdkColor *color = NULL;
-			gboolean set = FALSE;
-			char colcode[] = "COLOR=\"#XXXXXX\"";
-			g_object_get(G_OBJECT(tag), "foreground-set", &set, "foreground-gdk", &color, NULL);
-			if (set && color)
-				g_snprintf(colcode, sizeof(colcode), "COLOR=\"#%02x%02x%02x\"",
-						color->red >> 8, color->green >> 8, color->blue >> 8);
-			else
-				colcode[0] = '\0';
-			g_snprintf(buf2, BUF_LONG, "<FONT %s SIZE=\"2\"><!--%s --></FONT>", colcode, mdate);
+			const char *color = get_text_tag_color(tag);
+			g_snprintf(buf2, BUF_LONG, "<FONT %s%s%s SIZE=\"2\"><!--%s --></FONT>",
+					color ? "COLOR=\"" : "", color ? color : "", color ? "\"" : "", mdate);
 			gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all | GTK_IMHTML_NO_SCROLL);
-			if (color)
-				gdk_color_free(color);
 		}
 
 		gtk_text_buffer_get_end_iter(buffer, &end);
--- a/pidgin/gtkconv.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/gtkconv.h	Thu Nov 06 03:20:05 2008 +0000
@@ -143,7 +143,7 @@
 	GtkWidget *tab_label;
 	GtkWidget *menu_icon;
 	GtkWidget *menu_label;
-#ifndef PIDGIN_DISABLE_DEPRECATED
+#if !(defined PIDGIN_DISABLE_DEPRECATED) || (defined _PIDGIN_GTKCONV_C_)
 	/** @deprecated */
 	GtkSizeGroup *sg;
 #else
--- a/pidgin/gtkdialogs.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/gtkdialogs.c	Thu Nov 06 03:20:05 2008 +0000
@@ -23,6 +23,8 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+#define _PIDGIN_GTKDIALOGS_C_
+
 #include "internal.h"
 #include "pidgin.h"
 
@@ -69,37 +71,35 @@
 
 /* Order: Alphabetical by Last Name */
 static const struct developer developers[] = {
-	{"Daniel 'datallah' Atallah",	N_("developer"), NULL},
-	{"John 'rekkanoryo' Bailey",	N_("developer"), NULL},
-	{"Ethan 'Paco-Paco' Blanton",	N_("developer"), NULL},
-	{"Thomas Butter",				N_("developer"), NULL},
+	{"Daniel 'datallah' Atallah",	NULL, NULL},
+	{"John 'rekkanoryo' Bailey",	N_("bug master"), "rekkanoryo@pidgin.im"},
+	{"Ethan 'Paco-Paco' Blanton",	NULL, NULL},
+	{"Hylke Bons",			N_("artist"), "h.bons@student.rug.nl"},
+	{"Thomas Butter",				NULL, NULL},
 	/* feel free to not translate this */
-	{N_("Ka-Hing Cheung"),			N_("developer"), NULL},
-	{"Sadrul Habib Chowdhury",		N_("developer"), NULL},
-	{"Mark 'KingAnt' Doliner",		N_("developer"), "mark@kingant.net"},
-	{"Sean Egan",					N_("developer"), "sean.egan@gmail.com"},
-	{"Casey Harkins",               N_("developer"),   NULL},
-	{"Gary 'grim' Kramlich",		N_("developer"), NULL},
-	{"Richard 'rlaager' Laager",	N_("developer"), NULL},
-	{"Richard 'wabz' Nelson",		N_("developer"), NULL},
-	{"Christopher 'siege' O'Brien", N_("developer"), "taliesein@users.sf.net"},
-	{"Bartosz Oler",		N_("developer"), NULL},
-	{"Etan 'deryni' Reisner",       N_("developer"), NULL},
-	{"Tim 'marv' Ringenbach",		N_("developer"), NULL},
-	{"Elliott 'QuLogic' Sales de Andrade",	N_("developer"),	NULL},
+	{N_("Ka-Hing Cheung"),			NULL, NULL},
+	{"Sadrul Habib Chowdhury",		NULL, NULL},
+	{"Mark 'KingAnt' Doliner",		NULL, "mark@kingant.net"},
+	{"Sean Egan",					NULL, "sean.egan@gmail.com"},
+	{"Casey Harkins",               NULL,   NULL},
+	{"Gary 'grim' Kramlich",		NULL, "grim@pidgin.im"},
+	{"Richard 'rlaager' Laager",	NULL, "rlaager@pidgin.im"},
+	{"Richard 'wabz' Nelson",		NULL, NULL},
+	{"Christopher 'siege' O'Brien", NULL, "taliesein@users.sf.net"},
+	{"Bartosz Oler",		NULL, NULL},
+	{"Etan 'deryni' Reisner",       NULL, NULL},
+	{"Tim 'marv' Ringenbach",		NULL, NULL},
+	{"Elliott 'QuLogic' Sales de Andrade",	NULL,	NULL},
 	{"Luke 'LSchiere' Schierer",	N_("support"), "lschiere@users.sf.net"},
-	{"Megan 'Cae' Schneider",       N_("support/QA"), NULL},
-	{"Evan Schoenberg",		N_("developer"), NULL},
-	{"Kevin 'SimGuy' Stange",	N_("developer & webmaster"),	NULL},
-	{"Will 'resiak' Thompson",	N_("developer"),	NULL},
-	{"Stu 'nosnilmot' Tomlinson",	N_("developer"), NULL},
-	{"Nathan 'faceprint' Walp",		N_("developer"), NULL},
+	{"Evan Schoenberg",		NULL, NULL},
+	{"Kevin 'SimGuy' Stange",	N_("webmaster"),	NULL},
+	{"Will 'resiak' Thompson",	NULL,	NULL},
+	{"Stu 'nosnilmot' Tomlinson",	NULL, NULL},
 	{NULL, NULL, NULL}
 };
 
 /* Order: Alphabetical by Last Name */
 static const struct developer patch_writers[] = {
-	{"Felipe 'shx' Contreras",		NULL,	NULL},
 	{"Marcus 'malu' Lundblad", NULL, NULL},
 	{"Dennis 'EvilDennisR' Ristuccia",	N_("Senior Contributor/QA"),	NULL},
 	{"Peter 'Fmoo' Ruibal",		NULL,	NULL},
@@ -114,17 +114,20 @@
 	{"Jim Duchek",			N_("maintainer"), "jim@linuxpimps.com"},
 	{"Rob Flynn",			N_("maintainer"), NULL},
 	{"Adam Fritzler",		N_("libfaim maintainer"), NULL},
-	{"Christian 'ChipX86' Hammond",	N_("developer & webmaster"), NULL},
+	{"Christian 'ChipX86' Hammond",	N_("webmaster"), NULL},
 	/* If "lazy bum" translates literally into a serious insult, use something else or omit it. */
 	{"Syd Logan",			N_("hacker and designated driver [lazy bum]"), NULL},
-	{"Jim Seymour",			N_("XMPP developer"), NULL},
+	{"Megan 'Cae' Schneider",       N_("support/QA"), NULL},
+	{"Jim Seymour",			N_("XMPP"), NULL},
 	{"Mark Spencer",		N_("original author"), "markster@marko.net"},
+	{"Nathan 'faceprint' Walp",		NULL, NULL},
 	{"Eric Warmenhoven",	N_("lead developer"), "warmenhoven@yahoo.com"},
 	{NULL, NULL, NULL}
 };
 
 /* Order: Alphabetical by Last Name */
 static const struct developer retired_patch_writers[] = {
+	{"Felipe 'shx' Contreras",		NULL,	NULL},
 	{"Decklin Foster",				NULL,	NULL},
 	{"Peter 'Bleeter' Lawler",      NULL,   NULL},
 	{"Robert 'Robot101' McQueen",	NULL,	NULL},
@@ -133,7 +136,7 @@
 };
 
 /* Order: Code, then Alphabetical by Last Name */
-static const struct translator current_translators[] = {
+static const struct translator translators[] = {
 	{N_("Afrikaans"),           "af", "Samuel Murray", "afrikaans@gmail.com"},
 	{N_("Afrikaans"),           "af", "Friedel Wolff", "friedel@translate.org.za"},
 	{N_("Arabic"),              "ar", "Khaled Hosny", "khaledhosny@eglug.org"},
@@ -193,7 +196,7 @@
 	{N_("Macedonian"),          "mk", "Arangel Angov ", "arangel@linux.net.mk"},
 	{N_("Macedonian"),          "mk", "Ivana Kirkovska", "ivana.kirkovska@gmail.com"},
 	{N_("Macedonian"),          "mk", "Jovan Naumovski", "jovan@lugola.net"},
-	{"Mongolian",               "mn", "gooyo"},
+	{"Mongolian",               "mn", "gooyo", NULL},
 	{N_("Bokmål Norwegian"),    "nb", "Espen Stefansen", "espenas@gmail.com"},
 	{N_("Nepali"),              "ne", "Shyam Krishna Bal", "shyamkrishna_bal@yahoo.com"},
 	{N_("Dutch, Flemish"),      "nl", "Vincent van Adrighem", "V.vanAdrighem@dirck.mine.nu"},
@@ -285,10 +288,44 @@
 	{NULL, NULL, NULL, NULL}
 };
 
-static const struct artist artists[] = {
-	{"Hylke Bons",	"h.bons@student.rug.nl"},
-	{NULL, NULL}
-};
+static void
+add_developers(GString *str, const struct developer *list)
+{
+	for (; list->name != NULL; list++) {
+		if (list->email != NULL) {
+			g_string_append_printf(str, "  <a href=\"mailto:%s\">%s</a>%s%s%s<br/>",
+			                       list->email, _(list->name),
+			                       list->role ? " (" : "",
+			                       list->role ? _(list->role) : "",
+			                       list->role ? ")" : "");
+		} else {
+			g_string_append_printf(str, "  %s%s%s%s<br/>",
+			                       _(list->name),
+			                       list->role ? " (" : "",
+			                       list->role ? _(list->role) : "",
+			                       list->role ? ")" : "");
+		}
+	}
+}
+
+static void
+add_translators(GString *str, const struct translator *list)
+{
+	for (; list->language != NULL; list++) {
+		if (list->email != NULL) {
+			g_string_append_printf(str, "  <b>%s (%s)</b> - <a href=\"mailto:%s\">%s</a><br/>",
+			                       _(list->language),
+			                       list->abbr,
+			                       list->email,
+			                       _(list->name));
+		} else {
+			g_string_append_printf(str, "  <b>%s (%s)</b> - %s<br/>",
+			                       _(list->language),
+			                       list->abbr,
+			                       _(list->name));
+		}
+	}
+}
 
 void
 pidgin_dialogs_destroy_all()
@@ -355,7 +392,6 @@
 	GtkWidget *button;
 	GtkTextIter iter;
 	GString *str;
-	int i;
 	AtkObject *obj;
 	char* filename, *tmp;
 	GdkPixbuf *pixbuf;
@@ -426,116 +462,37 @@
 	/* Current Developers */
 	g_string_append_printf(str, "<FONT SIZE=\"4\">%s:</FONT><BR/>",
 						   _("Current Developers"));
-	for (i = 0; developers[i].name != NULL; i++) {
-		if (developers[i].email != NULL) {
-			g_string_append_printf(str, "  %s (%s) &lt;<a href=\"mailto:%s\">%s</a>&gt;<br/>",
-					_(developers[i].name), _(developers[i].role),
-					developers[i].email, developers[i].email);
-		} else {
-			g_string_append_printf(str, "  %s (%s)<br/>",
-					_(developers[i].name), _(developers[i].role));
-		}
-	}
+	add_developers(str, developers);
 	g_string_append(str, "<BR/>");
 
 	/* Crazy Patch Writers */
 	g_string_append_printf(str, "<FONT SIZE=\"4\">%s:</FONT><BR/>",
 						   _("Crazy Patch Writers"));
-	for (i = 0; patch_writers[i].name != NULL; i++) {
-		if (patch_writers[i].email != NULL) {
-			g_string_append_printf(str, "  %s &lt;<a href=\"mailto:%s\">%s</a>&gt;<br/>",
-					patch_writers[i].name,
-					patch_writers[i].email, patch_writers[i].email);
-		} else {
-			g_string_append_printf(str, "  %s<br/>",
-					patch_writers[i].name);
-		}
-	}
+	add_developers(str, patch_writers);
 	g_string_append(str, "<BR/>");
 
 	/* Retired Developers */
 	g_string_append_printf(str, "<FONT SIZE=\"4\">%s:</FONT><BR/>",
 						   _("Retired Developers"));
-	for (i = 0; retired_developers[i].name != NULL; i++) {
-		if (retired_developers[i].email != NULL) {
-			g_string_append_printf(str, "  %s (%s) &lt;<a href=\"mailto:%s\">%s</a>&gt;<br/>",
-					retired_developers[i].name, _(retired_developers[i].role),
-					retired_developers[i].email, retired_developers[i].email);
-		} else {
-			g_string_append_printf(str, "  %s (%s)<br/>",
-					retired_developers[i].name, _(retired_developers[i].role));
-		}
-	}
+	add_developers(str, retired_developers);
 	g_string_append(str, "<BR/>");
 
 	/* Retired Crazy Patch Writers */
 	g_string_append_printf(str, "<FONT SIZE=\"4\">%s:</FONT><BR/>",
 						   _("Retired Crazy Patch Writers"));
-	for (i = 0; retired_patch_writers[i].name != NULL; i++) {
-		if (retired_patch_writers[i].email != NULL) {
-			g_string_append_printf(str, "  %s &lt;<a href=\"mailto:%s\">%s</a>&gt;<br/>",
-					retired_patch_writers[i].name,
-					retired_patch_writers[i].email, retired_patch_writers[i].email);
-		} else {
-			g_string_append_printf(str, "  %s<br/>",
-					retired_patch_writers[i].name);
-		}
-	}
-	g_string_append(str, "<BR/>");
-
-	/* Artists */
-        g_string_append_printf(str, "<FONT SIZE=\"4\">%s:</FONT><BR/>",
-                                                   _("Artists"));
-        for (i = 0; artists[i].name != NULL; i++) {
-        	if (artists[i].email != NULL) {
-			g_string_append_printf(str, "  %s &lt;<a href=\"mailto:%s\">%s</a>&gt;<br/>",
-			                           artists[i].name,
-			                           artists[i].email, artists[i].email);
-	        } else {
-	                g_string_append_printf(str, "  %s<br/>",
-	                                      artists[i].name);
-	        }
-	}
+	add_developers(str, retired_patch_writers);
 	g_string_append(str, "<BR/>");
 			
 	/* Current Translators */
 	g_string_append_printf(str, "<FONT SIZE=\"4\">%s:</FONT><BR/>",
 						   _("Current Translators"));
-	for (i = 0; current_translators[i].language != NULL; i++) {
-		if (current_translators[i].email != NULL) {
-			g_string_append_printf(str, "  <b>%s (%s)</b> - %s &lt;<a href=\"mailto:%s\">%s</a>&gt;<br/>",
-							_(current_translators[i].language),
-							current_translators[i].abbr,
-							_(current_translators[i].name),
-							current_translators[i].email,
-							current_translators[i].email);
-		} else {
-			g_string_append_printf(str, "  <b>%s (%s)</b> - %s<br/>",
-							_(current_translators[i].language),
-							current_translators[i].abbr,
-							_(current_translators[i].name));
-		}
-	}
+	add_translators(str, translators);
 	g_string_append(str, "<BR/>");
 
 	/* Past Translators */
 	g_string_append_printf(str, "<FONT SIZE=\"4\">%s:</FONT><BR/>",
 						   _("Past Translators"));
-	for (i = 0; past_translators[i].language != NULL; i++) {
-		if (past_translators[i].email != NULL) {
-			g_string_append_printf(str, "  <b>%s (%s)</b> - %s &lt;<a href=\"mailto:%s\">%s</a>&gt;<br/>",
-							_(past_translators[i].language),
-							past_translators[i].abbr,
-							_(past_translators[i].name),
-							past_translators[i].email,
-							past_translators[i].email);
-		} else {
-			g_string_append_printf(str, "  <b>%s (%s)</b> - %s<br/>",
-							_(past_translators[i].language),
-							past_translators[i].abbr,
-							_(past_translators[i].name));
-		}
-	}
+	add_translators(str, past_translators);
 	g_string_append(str, "<BR/>");
 
 	g_string_append_printf(str, "<FONT SIZE=\"4\">%s</FONT><br/>", _("Debugging Information"));
@@ -1018,7 +975,7 @@
 static void
 pidgin_dialogs_alias_contact_cb(PurpleContact *contact, const char *new_alias)
 {
-	purple_contact_set_alias(contact, new_alias);
+	purple_blist_alias_contact(contact, new_alias);
 }
 
 void
--- a/pidgin/gtkdialogs.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/gtkdialogs.h	Thu Nov 06 03:20:05 2008 +0000
@@ -38,11 +38,13 @@
 void pidgin_dialogs_info(void);
 void pidgin_dialogs_log(void);
 
+#if !(defined PIDGIN_DISABLE_DEPRECATED) || (defined _PIDGIN_GTKDIALOGS_C_)
 /**
  * @deprecated This function is no longer used and will be removed in
  *             Pidgin 3.0.0 unless there is sufficient demand to keep it.
  */
 void pidgin_dialogs_alias_contact(PurpleContact *);
+#endif
 
 void pidgin_dialogs_alias_buddy(PurpleBuddy *);
 void pidgin_dialogs_alias_chat(PurpleChat *);
@@ -55,9 +57,12 @@
 /* Everything after this should probably be moved elsewhere */
 
 #ifndef PIDGIN_DISABLE_DEPRECATED
+/* This PIDGIN_DISABLE_DEPRECATED doesn't need to be deactivated by
+ * _PIDGIN_GTKDIALOGS_C_, because it shouldn't be using this macro. */
 #define PIDGIN_DIALOG(x)	x = gtk_window_new(GTK_WINDOW_TOPLEVEL); \
 			gtk_window_set_type_hint(GTK_WINDOW(x), GDK_WINDOW_TYPE_HINT_DIALOG)
 #endif
+
 #define PIDGIN_WINDOW_ICONIFIED(x) (gdk_window_get_state(GTK_WIDGET(x)->window) & GDK_WINDOW_STATE_ICONIFIED)
 
 #endif /* _PIDGINDIALOGS_H_ */
--- a/pidgin/gtkimhtml.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/gtkimhtml.c	Thu Nov 06 03:20:05 2008 +0000
@@ -24,6 +24,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
+#define _PIDGIN_GTKIMHTML_C_
 
 #ifdef HAVE_CONFIG_H
 #include <config.h>
@@ -348,9 +349,7 @@
 			g_string_free (t->values, TRUE);
 			g_free (t->children);
 		}
-		if (t && t->image) {
-			t->image->imhtml = NULL;
-		}
+		
 		g_free (t);
 	}
 }
@@ -1976,7 +1975,7 @@
 
 		pos = strchr (t->values->str, *x);
 		if (pos)
-			t = t->children [(unsigned int) pos - (unsigned int) t->values->str];
+			t = t->children [pos - t->values->str];
 		else
 			return;
 
@@ -2046,6 +2045,29 @@
 	return 0;
 }
 
+/* a hack-around to prevent trying to doing gtk_smiley_tree_remove on a
+ GtkIMHtml that no longer lives... I know this is ugly, but I couldn't find
+ a better way to handle it for now, since there lives a list GtkIMHtmlSmileys
+ in gtksmiley.c and those can end up having dangling imhtml pointers */
+static gboolean
+gtk_imhtml_is_alive(const GtkIMHtml *imhtml)
+{
+	GList *convs;
+	
+	for (convs = purple_get_conversations() ; convs != NULL ;
+		 convs = g_list_next(convs)) {
+		PurpleConversation *conv = (PurpleConversation *) convs->data;
+			 
+		if (PIDGIN_IS_PIDGIN_CONVERSATION(conv)) {
+			if (GTK_IMHTML(PIDGIN_CONVERSATION(conv)->imhtml) == imhtml
+				|| GTK_IMHTML(PIDGIN_CONVERSATION(conv)->entry) == imhtml) {
+				return TRUE;
+			}
+		}
+	}
+	return FALSE;
+}
+
 static void
 gtk_imhtml_disassociate_smiley_foreach(gpointer key, gpointer value,
 	gpointer user_data)
@@ -2058,10 +2080,11 @@
 static void
 gtk_imhtml_disassociate_smiley(GtkIMHtmlSmiley *smiley)
 {
-	if (smiley->imhtml) {
+	if (smiley->imhtml && gtk_imhtml_is_alive(smiley->imhtml)) {
 		gtk_smiley_tree_remove(smiley->imhtml->default_smilies, smiley);
 		g_hash_table_foreach(smiley->imhtml->smiley_data, 
 			gtk_imhtml_disassociate_smiley_foreach, smiley);
+		smiley->imhtml = NULL;
 	}
 }
 
@@ -2522,6 +2545,78 @@
 	}
 }
 
+/* CSS colors are either rgb (x,y,z) or #hex
+ * we need to convert to hex if it is RGB */
+static gchar*
+parse_css_color(gchar *in_color)
+{
+	char *tmp = in_color;
+
+	if (*tmp == 'r' && *(++tmp) == 'g' && *(++tmp) == 'b' && *(++tmp)) {
+		int rgbval[] = {0, 0, 0};
+		int count = 0;
+		const char *v_start;
+
+		while (*tmp && g_ascii_isspace(*tmp))
+			tmp++;
+		if (*tmp != '(') {
+			/* We don't support rgba() */
+			purple_debug_warning("gtkimhtml", "Invalid rgb CSS color in '%s'!\n", in_color);
+			return in_color;
+		}
+		tmp++;
+
+		while (count < 3) {
+			/* Skip any leading spaces */
+			while (*tmp && g_ascii_isspace(*tmp))
+				tmp++;
+
+			/* Find the subsequent contiguous digits */
+			v_start = tmp;
+			if (*v_start == '-')
+				tmp++;
+			while (*tmp && g_ascii_isdigit(*tmp))
+				tmp++;
+
+			if (tmp != v_start) {
+				char prev = *tmp;
+				*tmp = '\0';
+				rgbval[count] = atoi(v_start);
+				*tmp = prev;
+
+				/* deal with % */
+				while (*tmp && g_ascii_isspace(*tmp))
+					tmp++;
+				if (*tmp == '%') {
+					rgbval[count] = (rgbval[count] / 100.0) * 255;
+					tmp++;
+				}
+			} else {
+				purple_debug_warning("gtkimhtml", "Invalid rgb CSS color in '%s'!\n", in_color);
+				return in_color;
+			}
+
+			if (rgbval[count] > 255) {
+				rgbval[count] = 255;
+			} else if (rgbval[count] < 0) {
+				rgbval[count] = 0;
+			}
+
+			while (*tmp && g_ascii_isspace(*tmp))
+				tmp++;
+			if (*tmp == ',')
+				tmp++;
+
+			count++;
+		}
+		
+		g_free(in_color);
+		return g_strdup_printf("#%02X%02X%02X", rgbval[0], rgbval[1], rgbval[2]);
+	}
+
+	return in_color;
+}
+
 void gtk_imhtml_insert_html_at_iter(GtkIMHtml        *imhtml,
                                     const gchar      *text,
                                     GtkIMHtmlOptions  options,
@@ -2982,7 +3077,7 @@
 							oldfont = fonts->data;
 
 						if (color && !(options & GTK_IMHTML_NO_COLOURS) && (imhtml->format_functions & GTK_IMHTML_FORECOLOR)) {
-							font->fore = color;
+							font->fore = parse_css_color(color);
 							gtk_imhtml_toggle_forecolor(imhtml, font->fore);
 						} else {
 							if (oldfont && oldfont->fore)
@@ -2991,7 +3086,7 @@
 						}
 
 						if (background && !(options & GTK_IMHTML_NO_COLOURS) && (imhtml->format_functions & GTK_IMHTML_BACKCOLOR)) {
-							font->back = background;
+							font->back = parse_css_color(background);
 							gtk_imhtml_toggle_backcolor(imhtml, font->back);
 						} else {
 							if (oldfont && oldfont->back)
--- a/pidgin/gtkimhtml.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/gtkimhtml.h	Thu Nov 06 03:20:05 2008 +0000
@@ -130,7 +130,7 @@
 		GtkTextTag *link;
 	} edit;
 
-#ifndef PIDGIN_DISABLE_DEPRECATED
+#if !(defined PIDGIN_DISABLE_DEPRECATED) || (defined _PIDGIN_GTKIMHTML_C_)
 	/** @deprecated */
 	char *clipboard_text_string;
 	/** @deprecated */
--- a/pidgin/gtkmain.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/gtkmain.c	Thu Nov 06 03:20:05 2008 +0000
@@ -536,12 +536,8 @@
 			"Please make sure to specify what you were doing at the time\n"
 			"and post the backtrace from the core file.  If you do not know\n"
 			"how to get the backtrace, please read the instructions at\n"
-			"%swiki/GetABacktrace\n\n"
-			"If you need further assistance, please IM either SeanEgn or \n"
-			"LSchiere (via AIM).  Contact information for Sean and Luke \n"
-			"on other protocols is at\n"
-			"%swiki/DeveloperPages\n"),
-			PIDGIN_NAME, DISPLAY_VERSION, PURPLE_DEVEL_WEBSITE, PURPLE_DEVEL_WEBSITE, PURPLE_DEVEL_WEBSITE
+			"%swiki/GetABacktrace\n"),
+			PIDGIN_NAME, DISPLAY_VERSION, PURPLE_DEVEL_WEBSITE, PURPLE_DEVEL_WEBSITE
 		);
 
 		/* we have to convert the message (UTF-8 to console
--- a/pidgin/gtkprefs.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/gtkprefs.c	Thu Nov 06 03:20:05 2008 +0000
@@ -1837,14 +1837,14 @@
                 gpointer data)
 {
 	GtkToggleButton *button = data;
-	gboolean muted = val;
+	gboolean muted = GPOINTER_TO_INT(val);
 
 	g_return_if_fail(!strcmp (pref_name, PIDGIN_PREFS_ROOT "/sound/mute"));
 
 	/* Block the handler that re-sets the preference. */
-	g_signal_handlers_block_matched(button, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, pref_name);
+	g_signal_handlers_block_matched(button, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, (gpointer)pref_name);
 	gtk_toggle_button_set_active (button, muted);
-	g_signal_handlers_unblock_matched(button, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, pref_name);
+	g_signal_handlers_unblock_matched(button, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, (gpointer)pref_name);
 }
 
 
--- a/pidgin/gtksound.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/gtksound.c	Thu Nov 06 03:20:05 2008 +0000
@@ -447,7 +447,7 @@
 		if (!sound_cmd || *sound_cmd == '\0') {
 			purple_debug_error("gtksound",
 					 "'Command' sound method has been chosen, "
-					 "but no command has been set.");
+					 "but no command has been set.\n");
 			return;
 		}
 
--- a/pidgin/gtkstatusbox.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/gtkstatusbox.c	Thu Nov 06 03:20:05 2008 +0000
@@ -397,7 +397,8 @@
 	status_box->icon_box = gtk_event_box_new();
 	gtk_widget_set_parent(status_box->icon_box, GTK_WIDGET(status_box));
 	gtk_widget_show(status_box->icon_box);
-#if 0
+
+#if GTK_CHECK_VERSION(2,12,0)
 	gtk_widget_set_tooltip_text(status_box->icon_box,
 			status_box->account ? _("Click to change your buddyicon for this account.") :
 				_("Click to change your buddyicon for all accounts."));
--- a/pidgin/gtkutils.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/gtkutils.c	Thu Nov 06 03:20:05 2008 +0000
@@ -23,6 +23,8 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+#define _PIDGIN_GTKUTILS_C_
+
 #include "internal.h"
 #include "pidgin.h"
 
@@ -2878,7 +2880,6 @@
 }
 #endif /* ! Gtk 2.6.0 */
 
-#ifndef PURPLE_DISABLE_DEPRECATED
 void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename)
 {
 	PurpleBuddy *buddy;
@@ -2893,7 +2894,6 @@
 	contact = purple_buddy_get_contact(buddy);
 	purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, filename);
 }
-#endif
 
 char *pidgin_make_pretty_arrows(const char *str)
 {
--- a/pidgin/gtkutils.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/gtkutils.h	Thu Nov 06 03:20:05 2008 +0000
@@ -638,7 +638,7 @@
 											 GError **error);
 #endif
 
-#ifndef PURPLE_DISABLE_DEPRECATED
+#if !(defined PIDGIN_DISABLE_DEPRECATED) || (defined _PIDGIN_GTKUTILS_C_)
 /**
  * Set or unset a custom buddyicon for a user.
  *
--- a/pidgin/pidgintooltip.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/pidgintooltip.c	Thu Nov 06 03:20:05 2008 +0000
@@ -30,6 +30,9 @@
 #include "pidgintooltip.h"
 #include "debug.h"
 
+static gboolean enable_tooltips;
+static int tooltip_delay = -1;
+
 struct
 {
 	GtkWidget *widget;
@@ -56,6 +59,27 @@
 } PidginTooltipData;
 
 static void
+initialize_tooltip_delay()
+{
+#if GTK_CHECK_VERSION(2,14,0)
+	GtkSettings *settings;
+#endif
+
+	if (tooltip_delay != -1)
+		return;
+
+#if GTK_CHECK_VERSION(2,14,0)
+	settings = gtk_settings_get_default();
+
+	g_object_get(settings, "gtk-enable-tooltips", &enable_tooltips, NULL);
+	g_object_get(settings, "gtk-tooltip-timeout", &tooltip_delay, NULL);
+#else
+	tooltip_delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay");
+	enable_tooltips = (tooltip_delay != 0);
+#endif
+}
+
+static void
 destroy_tooltip_data(PidginTooltipData *data)
 {
 	gtk_tree_path_free(data->common.treeview.path);
@@ -280,14 +304,12 @@
 row_motion_cb(GtkWidget *tv, GdkEventMotion *event, gpointer userdata)
 {
 	GtkTreePath *path;
-	int delay;
 
 	if (event->window != gtk_tree_view_get_bin_window(GTK_TREE_VIEW(tv)))
 		return FALSE;    /* The cursor is probably on the TreeView's header. */
 
-	/* XXX: probably use something more generic? */
-	delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay");
-	if (delay == 0)
+	initialize_tooltip_delay();
+	if (!enable_tooltips)
 		return FALSE;
 
 	if (pidgin_tooltip.timeout) {
@@ -307,7 +329,7 @@
 	gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &pidgin_tooltip.tip_rect);
 	gtk_tree_path_free(path);
 
-	pidgin_tooltip.timeout = g_timeout_add(delay, (GSourceFunc)pidgin_tooltip_timeout, userdata);
+	pidgin_tooltip.timeout = g_timeout_add(tooltip_delay, (GSourceFunc)pidgin_tooltip_timeout, userdata);
 
 	return FALSE;
 }
@@ -337,13 +359,13 @@
 static gboolean
 widget_motion_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
 {
-	int delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay");
+	initialize_tooltip_delay();
 
 	pidgin_tooltip_destroy();
-	if (delay == 0)
+	if (!enable_tooltips)
 		return FALSE;
 
-	pidgin_tooltip.timeout = g_timeout_add(delay, (GSourceFunc)pidgin_tooltip_timeout, data);
+	pidgin_tooltip.timeout = g_timeout_add(tooltip_delay, (GSourceFunc)pidgin_tooltip_timeout, data);
 	return FALSE;
 }
 
--- a/pidgin/plugins/notify.c	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/plugins/notify.c	Thu Nov 06 03:20:05 2008 +0000
@@ -766,8 +766,7 @@
 	/* Urgent method button */
 	toggle = gtk_check_button_new_with_mnemonic(_("Set window manager \"_URGENT\" hint"));
 #else
-	/* TODO: When we're not string frozen, mark for translation */
-	toggle = gtk_check_button_new_with_mnemonic("_Flash window");
+	toggle = gtk_check_button_new_with_mnemonic(_("_Flash window"));
 #endif
 	gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0);
 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle),
--- a/pidgin/plugins/perl/common/gtkmodule.h	Thu Nov 06 02:21:16 2008 +0000
+++ b/pidgin/plugins/perl/common/gtkmodule.h	Thu Nov 06 03:20:05 2008 +0000
@@ -1,3 +1,7 @@
+/* Allow the Perl code to see deprecated functions, so we can continue to
+ * export them to Perl plugins. */
+#undef PIDGIN_DISABLE_DEPRECATED
+
 typedef struct group *Pidgin__Group;
 
 #define group perl_group
--- a/po/POTFILES.in	Thu Nov 06 02:21:16 2008 +0000
+++ b/po/POTFILES.in	Thu Nov 06 03:20:05 2008 +0000
@@ -138,6 +138,7 @@
 libpurple/protocols/qq/buddy_opt.c
 libpurple/protocols/qq/group.c
 libpurple/protocols/qq/group_im.c
+libpurple/protocols/qq/group_info.c
 libpurple/protocols/qq/group_internal.c
 libpurple/protocols/qq/group_join.c
 libpurple/protocols/qq/group_opt.c
@@ -147,7 +148,6 @@
 libpurple/protocols/qq/qq_network.c
 libpurple/protocols/qq/qq_process.c
 libpurple/protocols/qq/send_file.c
-libpurple/protocols/qq/sys_msg.c
 libpurple/protocols/sametime/sametime.c
 libpurple/protocols/silc/buddy.c
 libpurple/protocols/silc/chat.c
--- a/po/de.po	Thu Nov 06 02:21:16 2008 +0000
+++ b/po/de.po	Thu Nov 06 03:20:05 2008 +0000
@@ -11,15 +11,15 @@
 msgstr ""
 "Project-Id-Version: de\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-10-16 00:55-0700\n"
-"PO-Revision-Date: 2008-10-02 23:07+0200\n"
-"Last-Translator: Björn Voigt <bjoern@cs.tu-berlin.de>\n"
+"POT-Creation-Date: 2008-10-28 17:46+0100\n"
+"PO-Revision-Date: 2008-10-28 17:46+0100\n"
+"Last-Translator: Jochen Kemnade <jochenkemnade@web.de>\n"
 "Language-Team: Deutsch <de@li.org>\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
-"X-Generator: KBabel 1.11.4\n"
+"X-Generator: Lokalize 0.2\n"
 
 #. Translators may want to transliterate the name.
 #. It is not to be translated.
@@ -243,9 +243,6 @@
 msgid "You must give a name for the group to add."
 msgstr "Bitte geben Sie den Namen der Gruppe ein, die hinzugefügt werden soll."
 
-msgid "A group with the name already exists."
-msgstr "Es gibt schon eine Gruppe mit diesem Namen."
-
 msgid "Add Group"
 msgstr "Gruppe hinzufügen"
 
@@ -283,8 +280,8 @@
 msgid "Blocked"
 msgstr "Blockiert"
 
-msgid "View Log"
-msgstr "Mitschnitt anzeigen"
+msgid "Show when offline"
+msgstr "Anzeigen, wenn im Offline-Modus"
 
 #, c-format
 msgid "Please enter the new name for %s"
@@ -328,6 +325,9 @@
 msgid "Toggle Tag"
 msgstr "Markierung umkehren"
 
+msgid "View Log"
+msgstr "Mitschnitt anzeigen"
+
 #. General
 msgid "Nickname"
 msgstr "Spitzname"
@@ -1128,7 +1128,6 @@
 msgid "%s has sent you a message. (%s)"
 msgstr "%s hat Ihnen eine Nachricht gesendet. (%s)"
 
-#, c-format
 msgid "Unknown pounce event. Please report this!"
 msgstr "Unbekanntes Alarm-Ereignis. Bitte berichten Sie dieses Problem!"
 
@@ -1498,7 +1497,6 @@
 "Wenn eine neue Unterhaltung eröffnet wird, fügt dieses Plugin die letzte "
 "Unterhaltung in die aktuelle Unterhaltung ein."
 
-#, c-format
 msgid "Online"
 msgstr "Online"
 
@@ -1841,7 +1839,6 @@
 "Fehler beim Lesen vom Auflösungsprozess:\n"
 "%s"
 
-#, c-format
 msgid "Resolver process exited without answering our request"
 msgstr "Auflösungsprozess hat sich beendet ohne die Anfrage zu beantworten"
 
@@ -1932,7 +1929,6 @@
 msgid "Transfer of file %s complete"
 msgstr "Ãœbertragung der Datei %s ist komplett"
 
-#, c-format
 msgid "File transfer complete"
 msgstr "Dateiübertragung ist komplett"
 
@@ -1940,7 +1936,6 @@
 msgid "You canceled the transfer of %s"
 msgstr "Sie haben die Dateiübertragung von %s abgebrochen"
 
-#, c-format
 msgid "File transfer cancelled"
 msgstr "Dateiübertragung wurde abgebrochen"
 
@@ -2148,7 +2143,6 @@
 msgid "You are using %s, but this plugin requires %s."
 msgstr "Sie benutzen %s, aber dieses Plugin benötigt %s."
 
-#, c-format
 msgid "This plugin has not defined an ID."
 msgstr "Dieses Plugin hat keine ID definiert."
 
@@ -3044,7 +3038,6 @@
 #. get_yahoo_status_from_purple_status() returns YAHOO_STATUS_CUSTOM for
 #. * the generic away state (YAHOO_STATUS_TYPE_AWAY) with no message
 #. Away stuff
-#, c-format
 msgid "Away"
 msgstr "Abwesend"
 
@@ -3936,7 +3929,6 @@
 msgid "Extended Away"
 msgstr "Abwesend (erweitert)"
 
-#, c-format
 msgid "Do Not Disturb"
 msgstr "Nicht stören"
 
@@ -4711,220 +4703,166 @@
 "%s ist auf der lokalen Liste, aber nicht auf der Serverliste. Möchten Sie, "
 "dass der Buddy hinzugefügt wird?"
 
-#, c-format
 msgid "Unable to parse message"
 msgstr "Kann die Nachricht nicht parsen"
 
-#, c-format
 msgid "Syntax Error (probably a client bug)"
 msgstr "Syntaxfehler (wahrscheinlich ein Client-Bug)"
 
-#, c-format
 msgid "Invalid email address"
 msgstr "Ungültige E-Mail-Adresse"
 
-#, c-format
 msgid "User does not exist"
 msgstr "Benutzer existiert nicht"
 
-#, c-format
 msgid "Fully qualified domain name missing"
 msgstr "Der Fully Qualified Domain Name fehlt"
 
-#, c-format
 msgid "Already logged in"
 msgstr "Schon angemeldet"
 
-#, c-format
 msgid "Invalid username"
 msgstr "Ungültiger Benutzername"
 
-#, c-format
 msgid "Invalid friendly name"
 msgstr "Ungültiger Freundesname"
 
-#, c-format
 msgid "List full"
 msgstr "Liste voll"
 
-#, c-format
 msgid "Already there"
 msgstr "Schon da"
 
-#, c-format
 msgid "Not on list"
 msgstr "Nicht auf der Liste"
 
-#, c-format
 msgid "User is offline"
 msgstr "Benutzer ist offline"
 
-#, c-format
 msgid "Already in the mode"
 msgstr "Bereits in diesem Modus"
 
-#, c-format
 msgid "Already in opposite list"
 msgstr "Bereits in der „Gegenteil-Liste“"
 
-#, c-format
 msgid "Too many groups"
 msgstr "Zu viele Gruppen"
 
-#, c-format
 msgid "Invalid group"
 msgstr "Ungültige Gruppe"
 
-#, c-format
 msgid "User not in group"
 msgstr "Benutzer ist nicht in der Gruppe"
 
-#, c-format
 msgid "Group name too long"
 msgstr "Name der Gruppe ist zu lang"
 
-#, c-format
 msgid "Cannot remove group zero"
 msgstr "Kann die Gruppe „Null“ nicht entfernen"
 
-#, c-format
 msgid "Tried to add a user to a group that doesn't exist"
 msgstr ""
 "Versuchte einen Benutzer zu einer nichtexistierenden Gruppe hinzuzufügen"
 
-#, c-format
 msgid "Switchboard failed"
 msgstr "Vermittlung gescheitert"
 
-#, c-format
 msgid "Notify transfer failed"
 msgstr "Ãœbertragung der Benachrichtigung gescheitert"
 
-#, c-format
 msgid "Required fields missing"
 msgstr "Notwendige Felder fehlen"
 
-#, c-format
 msgid "Too many hits to a FND"
 msgstr "Zu viele Treffer zu einem FND"
 
-#, c-format
 msgid "Not logged in"
 msgstr "Nicht angemeldet"
 
-#, c-format
 msgid "Service temporarily unavailable"
 msgstr "Dienst momentan nicht verfügbar"
 
-#, c-format
 msgid "Database server error"
 msgstr "Fehler des Datenbank-Servers"
 
-#, c-format
 msgid "Command disabled"
 msgstr "Kommando abgeschaltet"
 
-#, c-format
 msgid "File operation error"
 msgstr "Dateiverarbeitungsfehler"
 
-#, c-format
 msgid "Memory allocation error"
 msgstr "Fehler bei der Speicheranforderung"
 
-#, c-format
 msgid "Wrong CHL value sent to server"
 msgstr "Falscher CHL-Wert zum Server gesendet"
 
-#, c-format
 msgid "Server busy"
 msgstr "Server beschäftigt"
 
-#, c-format
 msgid "Server unavailable"
 msgstr "Server unerreichbar"
 
-#, c-format
 msgid "Peer notification server down"
 msgstr "Peer-Benachrichtigungsserver nicht erreichbar"
 
-#, c-format
 msgid "Database connect error"
 msgstr "Datenbank-Verbindungsfehler"
 
-#, c-format
 msgid "Server is going down (abandon ship)"
 msgstr "Server fährt runter (melden Sie sich ab)"
 
-#, c-format
 msgid "Error creating connection"
 msgstr "Fehler beim Herstellen der Verbindung"
 
-#, c-format
 msgid "CVR parameters are either unknown or not allowed"
 msgstr "CVR-Parameter sind entweder unbekannt oder nicht erlaubt"
 
-#, c-format
 msgid "Unable to write"
 msgstr "Schreiben nicht möglich"
 
-#, c-format
 msgid "Session overload"
 msgstr "Sitzung überlastet"
 
-#, c-format
 msgid "User is too active"
 msgstr "Benutzer ist zu aktiv"
 
-#, c-format
 msgid "Too many sessions"
 msgstr "Zu viele Sitzungen"
 
-#, c-format
 msgid "Passport not verified"
 msgstr "Passport (MSN Benutzerausweis) wurde nicht überprüft"
 
-#, c-format
 msgid "Bad friend file"
 msgstr "Falsche Friends-Datei"
 
-#, c-format
 msgid "Not expected"
 msgstr "Nicht erwartet"
 
-#, c-format
 msgid "Friendly name changes too rapidly"
 msgstr "Benutzernamen werden zu oft geändert"
 
-#, c-format
 msgid "Server too busy"
 msgstr "Server ist zu beschäftigt"
 
-#, c-format
 msgid "Authentication failed"
 msgstr "Authentifizierung fehlgeschlagen"
 
-#, c-format
 msgid "Not allowed when offline"
 msgstr "Nicht erlaubt im Offline-Modus"
 
-#, c-format
 msgid "Not accepting new users"
 msgstr "Akzeptiert keine neuen Benutzer"
 
-#, c-format
 msgid "Kids Passport without parental consent"
 msgstr "Kinder-Passwort ohne die Zustimmung der Eltern"
 
-#, c-format
 msgid "Passport account not yet verified"
 msgstr "Passport-Konto wurde noch nicht überprüft"
 
-#, c-format
 msgid "Passport account suspended"
 msgstr "Passport-Konto gesperrt"
 
-#, c-format
 msgid "Bad ticket"
 msgstr "Falsches Ticket"
 
@@ -5290,6 +5228,7 @@
 msgid "%s just sent you a Nudge!"
 msgstr "%s hat Sie gerade angestoßen!"
 
+#. char *adl = g_strndup(payload, len);
 #, c-format
 msgid "Unknown error (%d)"
 msgstr "Unbekannter Fehler (%d)"
@@ -6155,7 +6094,6 @@
 msgid "Error. SSL support is not installed."
 msgstr "Fehler. SSL ist nicht installiert."
 
-#, c-format
 msgid "This conference has been closed. No more messages can be sent."
 msgstr ""
 "Diese Konferenz wurde geschlossen. Es können keine Nachrichten mehr gesendet "
@@ -6425,23 +6363,18 @@
 msgid "Screen Sharing"
 msgstr "Gemeinsamer Bildschirm"
 
-#, c-format
 msgid "Free For Chat"
 msgstr "Bereit zum Chatten"
 
-#, c-format
 msgid "Not Available"
 msgstr "Nicht verfügbar"
 
-#, c-format
 msgid "Occupied"
 msgstr "Beschäftigt"
 
-#, c-format
 msgid "Web Aware"
 msgstr "In Web"
 
-#, c-format
 msgid "Invisible"
 msgstr "Unsichtbar"
 
@@ -7137,7 +7070,6 @@
 msgid "Attempting to connect to %s:%hu."
 msgstr "Verbindungsversuch mit %s:%hu."
 
-#, c-format
 msgid "Attempting to connect via proxy server."
 msgstr "Verbindungsversuch über einen Proxyserver."
 
@@ -7675,7 +7607,6 @@
 msgstr "Verbindung verloren"
 
 #. Update the login progress status display
-#, c-format
 msgid "Request token"
 msgstr "Anfragekürzel"
 
@@ -8379,7 +8310,6 @@
 msgid "<br><b>Channel Topic:</b><br>%s"
 msgstr "<br><b>Thema des Kanals:</b><br>%s"
 
-#, c-format
 msgid "<br><b>Channel Modes:</b> "
 msgstr "<br><b>Kanal-Modi:</b> "
 
@@ -8404,7 +8334,6 @@
 msgid "Channel Public Keys List"
 msgstr "Liste der öffentlichen Schlüssel des Kanals"
 
-#, c-format
 msgid ""
 "Channel authentication is used to secure the channel from unauthorized "
 "access. The authentication may be based on passphrase and digital "
@@ -8809,7 +8738,6 @@
 msgid "Your Current Mood"
 msgstr "Ihre momentane Stimmung"
 
-#, c-format
 msgid "Normal"
 msgstr "Normal"
 
@@ -9195,47 +9123,37 @@
 msgid "No server statistics available"
 msgstr "Keine Serverstatistik verfügbar"
 
-#, c-format
 msgid "Failure: Version mismatch, upgrade your client"
 msgstr "Fehler: Unterschiedliche Version, aktualisieren Sie Ihren Client"
 
-#, c-format
 msgid "Failure: Remote does not trust/support your public key"
 msgstr ""
 "Fehler: Die entfernte Seite vertraut Ihrem öffentlichen Schlüssel nicht"
 
-#, c-format
 msgid "Failure: Remote does not support proposed KE group"
 msgstr ""
 "Fehler: Entferntes Programm unterstützt nicht die vorgeschlagen KE-Gruppe"
 
-#, c-format
 msgid "Failure: Remote does not support proposed cipher"
 msgstr ""
 "Fehler: Entferntes Programm unterstützt die vorgeschlagene Cipher nicht"
 
-#, c-format
 msgid "Failure: Remote does not support proposed PKCS"
 msgstr "Fehler: Entferntes Programm unterstützt die vorgeschlagene PKCS nicht"
 
-#, c-format
 msgid "Failure: Remote does not support proposed hash function"
 msgstr ""
 "Fehler: Entferntes Programm unterstützt die vorgeschlagen Hashfunktion nicht"
 
-#, c-format
 msgid "Failure: Remote does not support proposed HMAC"
 msgstr "Fehler: Entferntes Programm unterstützt das vorgeschlagene HMAC nicht"
 
-#, c-format
 msgid "Failure: Incorrect signature"
 msgstr "Fehler: Falsche Signatur"
 
-#, c-format
 msgid "Failure: Invalid cookie"
 msgstr "Fehler: Ungültiger Cookie"
 
-#, c-format
 msgid "Failure: Authentication failed"
 msgstr "Fehler: Authentifizierung fehlgeschlagen"
 
@@ -9332,7 +9250,6 @@
 msgid "Warning of %s not allowed."
 msgstr "Verwarnung von %s nicht erlaubt."
 
-#, c-format
 msgid "A message has been dropped, you are exceeding the server speed limit."
 msgstr ""
 "Eine Nachricht ging verloren. Sie überschreiten die Geschwindigkeitsgrenze "
@@ -9356,39 +9273,30 @@
 "Eine Nachricht von %s hat Sie nicht erreicht, da sie zu schnell gesendet "
 "wurde."
 
-#, c-format
 msgid "Failure."
 msgstr "Fehler."
 
-#, c-format
 msgid "Too many matches."
 msgstr "Zu viele Ãœbereinstimmungen."
 
-#, c-format
 msgid "Need more qualifiers."
 msgstr "Benötige mehr Angaben."
 
-#, c-format
 msgid "Dir service temporarily unavailable."
 msgstr "Verzeichnis-Dienst ist zur Zeit nicht verfügbar."
 
-#, c-format
 msgid "Email lookup restricted."
 msgstr "E-Mail-Suche eingeschränkt."
 
-#, c-format
 msgid "Keyword ignored."
 msgstr "Stichwort ignoriert."
 
-#, c-format
 msgid "No keywords."
 msgstr "Keine Stichwörter."
 
-#, c-format
 msgid "User has no directory information."
 msgstr "Der Benutzer hat kein Profil."
 
-#, c-format
 msgid "Country not supported."
 msgstr "Land nicht unterstützt."
 
@@ -9396,19 +9304,15 @@
 msgid "Failure unknown: %s."
 msgstr "Unbekannter Fehler: %s."
 
-#, c-format
 msgid "Incorrect username or password."
 msgstr "Ungültiger Benutzername oder Passwort."
 
-#, c-format
 msgid "The service is temporarily unavailable."
 msgstr "Der Dienst ist zur Zeit nicht verfügbar."
 
-#, c-format
 msgid "Your warning level is currently too high to log in."
 msgstr "Ihre Warnstufe ist zur Zeit zu hoch, um sich anzumelden."
 
-#, c-format
 msgid ""
 "You have been connecting and disconnecting too frequently.  Wait ten minutes "
 "and try again.  If you continue to try, you will need to wait even longer."
@@ -10252,29 +10156,24 @@
 msgstr " (%s)"
 
 #. 10053
-#, c-format
 msgid "Connection interrupted by other software on your computer."
 msgstr ""
 "Die Verbindung wurde von einer anderen Software auf ihrem Computer "
 "unterbrochen."
 
 #. 10054
-#, c-format
 msgid "Remote host closed connection."
 msgstr "Der entfernte Host hat die Verbindung beendet."
 
 #. 10060
-#, c-format
 msgid "Connection timed out."
 msgstr "Verbindungsabbruch wegen Zeitüberschreitung."
 
 #. 10061
-#, c-format
 msgid "Connection refused."
 msgstr "Verbindung abgelehnt."
 
 #. 10048
-#, c-format
 msgid "Address already in use."
 msgstr "Adresse wird bereits benutzt."
 
@@ -10483,9 +10382,6 @@
 msgid "Hide when offline"
 msgstr "Verstecken, wenn im Offline-Modus"
 
-msgid "Show when offline"
-msgstr "Anzeigen, wenn im Offline-Modus"
-
 msgid "_Alias..."
 msgstr "_Alias..."
 
@@ -10883,9 +10779,6 @@
 msgid "SSL Servers"
 msgstr "SSL-Server"
 
-msgid "Network disconnected"
-msgstr "vom Netzwerk abgemeldet"
-
 msgid "Unknown command."
 msgstr "Unbekanntes Kommando."
 
@@ -11228,8 +11121,11 @@
 msgid "Fatal Error"
 msgstr "Schwerer Fehler"
 
-msgid "developer"
-msgstr "Entwickler"
+msgid "bug master"
+msgstr "Bug-Master"
+
+msgid "artist"
+msgstr "Künstler"
 
 #. feel free to not translate this
 msgid "Ka-Hing Cheung"
@@ -11238,11 +11134,8 @@
 msgid "support"
 msgstr "Support"
 
-msgid "support/QA"
-msgstr "Support/Qualitätssicherung"
-
-msgid "developer & webmaster"
-msgstr "Entwickler & Webmaster"
+msgid "webmaster"
+msgstr "Webmaster"
 
 msgid "Senior Contributor/QA"
 msgstr "Senior-Beitragender/QA"
@@ -11260,8 +11153,11 @@
 msgid "hacker and designated driver [lazy bum]"
 msgstr "Grafische Benutzeroberfläche"
 
-msgid "XMPP developer"
-msgstr "XMPP-Entwickler"
+msgid "support/QA"
+msgstr "Support/Qualitätssicherung"
+
+msgid "XMPP"
+msgstr "XMPP"
 
 msgid "original author"
 msgstr "Originalautor"
@@ -11508,7 +11404,6 @@
 "geschützt.  Die Datei 'COPYRIGHT' enthält die komplette Liste der "
 "Mitwirkenden.  Wir übernehmen keine Haftung für dieses Programm.<BR><BR>"
 
-#, c-format
 msgid "<FONT SIZE=\"4\">IRC:</FONT> #pidgin on irc.freenode.net<BR><BR>"
 msgstr "<FONT SIZE=\"4\">IRC:</FONT> #pidgin auf irc.freenode.net<BR><BR>"
 
@@ -11524,9 +11419,6 @@
 msgid "Retired Crazy Patch Writers"
 msgstr "Zurückgetretene verrückte Patchschreiber"
 
-msgid "Artists"
-msgstr "Künstler"
-
 msgid "Current Translators"
 msgstr "Aktuelle Ãœbersetzer"
 
@@ -11879,11 +11771,9 @@
 msgid "Save Image"
 msgstr "Bild speichern"
 
-#, c-format
 msgid "_Save Image..."
 msgstr "Bild _speichern..."
 
-#, c-format
 msgid "_Add Custom Smiley..."
 msgstr "Benutzerdefinierten Smiley _hinzufügen..."
 
@@ -12608,27 +12498,21 @@
 msgid "Sound Selection"
 msgstr "Klang-Auswahl"
 
-#, c-format
 msgid "Quietest"
 msgstr "Am leisesten"
 
-#, c-format
 msgid "Quieter"
 msgstr "Leiser"
 
-#, c-format
 msgid "Quiet"
 msgstr "Leise"
 
-#, c-format
 msgid "Loud"
 msgstr "Laut"
 
-#, c-format
 msgid "Louder"
 msgstr "Lauter"
 
-#, c-format
 msgid "Loudest"
 msgstr "Am lautesten"
 
@@ -13440,10 +13324,10 @@
 msgstr "Zeichne eine Markierunglinie in "
 
 msgid "_IM windows"
-msgstr "_IM-Fenstern"
+msgstr "_IM-Fenster"
 
 msgid "C_hat windows"
-msgstr "C_hat-Fenstern"
+msgstr "C_hat-Fenster"
 
 msgid ""
 "A music messaging session has been requested. Please click the MM icon to "
@@ -13525,6 +13409,9 @@
 msgid "Set window manager \"_URGENT\" hint"
 msgstr "Setze den „_URGENT“-Hinweis für den Window-Manager"
 
+msgid "_Flash window"
+msgstr "Fenster _blinken lassen"
+
 #. Raise window method button
 msgid "R_aise conversation window"
 msgstr "G_esprächsfenster in den Vordergrund bringen"
@@ -13635,7 +13522,6 @@
 msgid "Select Color"
 msgstr "Farbe auswählen"
 
-#, c-format
 msgid "Select Interface Font"
 msgstr "Schriftart wählen"
 
@@ -13862,7 +13748,6 @@
 msgid "Timestamp Format Options"
 msgstr "Zeitstempelformat-Optionen"
 
-#, c-format
 msgid "_Force 24-hour time format"
 msgstr "_Erzwinge 24-Stunden Zeitformat"
 
--- a/share/ca-certs/Makefile.am	Thu Nov 06 02:21:16 2008 +0000
+++ b/share/ca-certs/Makefile.am	Thu Nov 06 03:20:05 2008 +0000
@@ -3,6 +3,7 @@
 		CAcert_Class3.pem \
 		Equifax_Secure_CA.pem \
 		GTE_CyberTrust_Global_Root.pem \
+		Microsoft_Internet_Authority.pem \
 		Microsoft_Secure_Server_Authority.pem \
 		StartCom_Free_SSL_CA.pem \
 		Verisign_RSA_Secure_Server_CA.pem \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/ca-certs/Microsoft_Internet_Authority.pem	Thu Nov 06 03:20:05 2008 +0000
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIECzCCA3SgAwIBAgIEBAAD/jANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTA2MDQxOTE0MzUwMFoXDTA5MDQxOTIzNTkwMFowJzElMCMG
+A1UEAxMcTWljcm9zb2Z0IEludGVybmV0IEF1dGhvcml0eTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBALUIbk0YdsTXnGPswqx8d/Ntrsjy8Wau8cKHBGBu
+KZwAZe5qW+QOU4gRxiKbM/trspEu1lObU77PVtiZSqFQMkXOYNLhlq1a/eqh68RJ
+Jxxev9KWmfppQ388ONqhi8wziHoXc66RUCiqabp751dbmwnnTN6GfIR952Zg+ab1
+wmGL3o7B1efCMCI9LIMKsId16yHiXKbTBHuWnkAe4Qx2BMAgoJQQ21EbTBhyvCfd
+EiRdOdYo1OTe2xih4JUPmXf7xPNDjMCrpjEJ8woxgCnH12z7PNPqwrhnFe/6808t
+axy4iNgObXcZS3ERcZBA9RFT1z3onQ2E2plkaBPmZQPiZu0CAwEAAaOCAXAwggFs
+MEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly93d3cucHVibGljLXRydXN0LmNvbS9j
+Z2ktYmluL0NSTC8yMDE4L2NkcC5jcmwwHQYDVR0OBBYEFDNf3Q+3nFzO7ofdcHCL
+X33PIry5MFQGA1UdIARNMEswSQYKKoZIhvhjAQIBBTA7MDkGCCsGAQUFBwIBFi1o
+dHRwOi8vd3d3LnB1YmxpYy10cnVzdC5jb20vQ1BTL09tbmlSb290Lmh0bWwwgYkG
+A1UdIwSBgTB/oXmkdzB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBv
+cmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4x
+IzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEdsb2JhbCBSb290ggIBpTAOBgNVHQ8B
+Af8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBAjANBgkqhkiG9w0BAQUFAAOBgQBj
+SQlU7cXbnngZAIOa4zci+1Z2XFTTyOFc/Tfc0qU/xVWPZPBJdx2UVk2yCwmIHFBY
+OJSQC+7Kn7GE5nE3rBAyBrnB0oymBcBeD8tT3B4B31jHfnFgsC9UYin9uJN144+e
+tbzOegUg4qaNApAaWGre3YY7ALn1y/6XgqIEIEZcCQ==
+-----END CERTIFICATE-----
--- a/share/ca-certs/Microsoft_Secure_Server_Authority.pem	Thu Nov 06 02:21:16 2008 +0000
+++ b/share/ca-certs/Microsoft_Secure_Server_Authority.pem	Thu Nov 06 03:20:05 2008 +0000
@@ -1,28 +1,30 @@
 -----BEGIN CERTIFICATE-----
-MIIE1TCCA72gAwIBAgIKYSsApAADAAAAFDANBgkqhkiG9w0BAQUFADAnMSUwIwYD
-VQQDExxNaWNyb3NvZnQgSW50ZXJuZXQgQXV0aG9yaXR5MB4XDTA2MDQyMTE5MTEw
-NFoXDTA5MDQxOTIzNTkwMFowgYsxEzARBgoJkiaJk/IsZAEZFgNjb20xGTAXBgoJ
+MIIFEzCCA/ugAwIBAgIKYQVOAQADAAAAHDANBgkqhkiG9w0BAQUFADAnMSUwIwYD
+VQQDExxNaWNyb3NvZnQgSW50ZXJuZXQgQXV0aG9yaXR5MB4XDTA3MDkyODIyMDYz
+N1oXDTA5MDQxOTIzNTkwMFowgYsxEzARBgoJkiaJk/IsZAEZFgNjb20xGTAXBgoJ
 kiaJk/IsZAEZFgltaWNyb3NvZnQxFDASBgoJkiaJk/IsZAEZFgRjb3JwMRcwFQYK
 CZImiZPyLGQBGRYHcmVkbW9uZDEqMCgGA1UEAxMhTWljcm9zb2Z0IFNlY3VyZSBT
 ZXJ2ZXIgQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
-syn2tJzCJKsQy98cYrzBT0qMSqTMibRel34u64jqd1GKP9cRMHagyNLZIb1rv8NZ
-XPdf2uI8hGc4lxRh4iprbFPOZSiHQIyq9zoqLS7Bh/KnqnRX4CCntCQbWVa/s5c5
-FbGD5XG5AvA8sjn56WPotYLGeWrSZ/k2bcH9YgvQ2Yrt8t1VxPQciSuX82KSn2Kx
-xxJKgQtAgZpyekR5QdIUUvQsEAPaCfWRsF/OMQoyc4UeAxAELavr2vlq869ye3Fu
-f3fUhVEk8kxtiVPYWYBTAhWts62pEwaKGPteMsGnvbX+BRgIwc55vfs6KWXxnBUL
-9Q8BfQQLbT8nQe7hO4XXMQIDAQABo4IBnDCCAZgwEgYDVR0TAQH/BAgwBgEB/wIB
-ATAdBgNVHQ4EFgQUp08F+9GOQVM3lcpL4UMfWutNzVAwCwYDVR0PBAQDAgGGMBIG
-CSsGAQQBgjcVAQQFAgMDAAMwIwYJKwYBBAGCNxUCBBYEFMnAnEM4ke3T6fpSclUI
-g7XN8o57MBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMB8GA1UdIwQYMBaAFDNf
-3Q+3nFzO7ofdcHCLX33PIry5MGYGA1UdHwRfMF0wW6BZoFeGNGh0dHA6Ly9jcmwu
-bWljcm9zb2Z0LmNvbS9wa2kvbXNjb3JwL2NybC9tc3d3dygzKS5jcmyGH2h0dHA6
-Ly9jb3JwcGtpL2NybC9tc3d3dygzKS5jcmwweQYIKwYBBQUHAQEEbTBrMDwGCCsG
-AQUFBzAChjBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL21zY29ycC9tc3d3
-dygzKS5jcnQwKwYIKwYBBQUHMAKGH2h0dHA6Ly9jb3JwcGtpL2FpYS9tc3d3dygz
-KS5jcnQwDQYJKoZIhvcNAQEFBQADggEBAIJDQpkqhodC3ZmKPB4dTveoFCpuc4C0
-qdXPLggvRiTnQJP/SRdnfQwkaulDS3771zWo3fO1/tTiCmVvtU4LNbISpQmrz41q
-AP2QIUl/d4Fjiaz0qchNxmTRlVLGsTrwkMMkOh9MUQEcNyk9RuGepf/1+66qeEnx
-eCgK09yIcbgdE9ffk5ueTX7TNfgoNZO9ORnRS1p7nra3b0Wi1kdGqDmbpw7We+M8
-6B8VpPa0QaofgRdl2dzOijd7hGN+cgRNMvdGpRxzkaWUA52rBOrEVyJLu2IUksPV
-0Q/0ZZMNZXJh8MmWW/o8ebQZJQshN5p4M7HiUN/cZ2onnrdZSpIwyAU=
+xcCphW3RnMyuq1EAwlSsUghQUuCSQAaeFBTro1qbeCJEALHQGrXg8SMnY9Om64WG
+PnB9HhOb1LTOdkMVx6a3GU0mcv+LkefcW/CewIMfDXPoeGA4ULmY9wbraPwJY8Ba
+H3PAvOVSTJ0k9ZX6tXjE0D/214kn0+ZN+Y+ZwoqFC5/lgQyUxxgfVA0FsR/NDNF6
+keBsoNz2vPghAsruLhkON+5KGQo0o+T+XcJ7xNs7eJR7MWZn2rNCM1J+qrR0o2vG
+vz8QBSKogUXiPRXYVkNSkBEFZqGvEob1pDwC5fJEP71J6xEJHsSe0pcaa637RU04
+4BsjRHqJE3Rrlurb6Fc8vQIDAQABo4IB2jCCAdYwEgYDVR0TAQH/BAgwBgEB/wIB
+ATAdBgNVHQ4EFgQUmY+l9x6Bb/p5wvAWP7JUsQhoR1UwCwYDVR0PBAQDAgGGMBIG
+CSsGAQQBgjcVAQQFAgMEAAQwIwYJKwYBBAGCNxUCBBYEFBOB4Tms57aqAqhuDUms
+pmqjrALsMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMB8GA1UdIwQYMBaAFDNf
+3Q+3nFzO7ofdcHCLX33PIry5MIGjBgNVHR8EgZswgZgwgZWggZKggY+GNmh0dHA6
+Ly9tc2NybC5taWNyb3NvZnQuY29tL3BraS9tc2NvcnAvY3JsL21zd3d3KDMpLmNy
+bIY0aHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9tc2NvcnAvY3JsL21zd3d3
+KDMpLmNybIYfaHR0cDovL2NvcnBwa2kvY3JsL21zd3d3KDMpLmNybDB5BggrBgEF
+BQcBAQRtMGswPAYIKwYBBQUHMAKGMGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w
+a2kvbXNjb3JwL21zd3d3KDMpLmNydDArBggrBgEFBQcwAoYfaHR0cDovL2NvcnBw
+a2kvYWlhL21zd3d3KDMpLmNydDANBgkqhkiG9w0BAQUFAAOCAQEARSzU1qmSJczv
+IiFrscxgYtzXekBWgm2rYVIqN996H5ZfMDaso0tZXmhWo0zX0z0r0H+IIGtJ+4D9
+t6dMTpB26jV/5EtMbtQu48ryCkMHBKNtCn7pQ5Mq2gqjq84KrVUlBFfPT4z4+Hqp
+Q6gVghMggWNtiujdHh7w4ja3OqHkFZTX/wUSTb+BtH+GBZKYAGU6duakb3D0piFu
+W5W6gZ4j15R12ys3ZYTKThZjW5blolA0lzuCSghoSfJHRA4jChIzDNojkf7OxqJw
+yFl8+++iZwTiulCTgOucJxGCT1tEEwdfX2c+XKyJIS/d5Ndtk91WEWU+TzqX9ZT4
+SAMJpIZ1pg==
 -----END CERTIFICATE-----