changeset 25216:e17605cc2c0a

propagate from branch 'im.pidgin.pidgin' (head 37145f1b4ec49b432ea571f96562d44f4c5a908e) to branch 'im.pidgin.cpw.malu.xmpp.attention' (head 06a2198ca8b2167d40d28ae761e461d104907fd9)
author Marcus Lundblad <ml@update.uu.se>
date Wed, 29 Oct 2008 21:38:53 +0000
parents a4dfc631b140 (current diff) b6dc71133eb8 (diff)
children 2f01775c81d5
files 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/sys_msg.c libpurple/protocols/qq/sys_msg.h
diffstat 59 files changed, 7101 insertions(+), 5407 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Mon Oct 27 23:13:11 2008 +0000
+++ b/NEWS	Wed Oct 29 21:38:53 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
--- a/libpurple/protocols/qq/AUTHORS	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/AUTHORS	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/ChangeLog	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/Makefile.am	Wed Oct 29 21:38:53 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
 
@@ -63,6 +53,11 @@
 
 libqq_la_LDFLAGS = -module -avoid-version
 
+CURRENT_REVISION=$(shell \
+		 awk 'BEGIN {"grep node .hg_archival.txt" | getline rev; \
+		 rev=substr(rev,7,6); \
+		 print rev}')
+
 if STATIC_QQ
 
 st = -DPURPLE_STATIC_PRPL
@@ -84,4 +79,5 @@
 	-I$(top_builddir)/libpurple \
 	-DQQ_BUDDY_ICON_DIR=\"$(datadir)/pixmaps/purple/buddy_icons/qq\" \
 	$(DEBUG_CFLAGS) \
-	$(GLIB_CFLAGS)
+	$(GLIB_CFLAGS) \
+	-DOPENQ_VERSION=\"$(CURRENT_REVISION)\"
--- a/libpurple/protocols/qq/Makefile.mingw	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/Makefile.mingw	Wed Oct 29 21:38:53 2008 +0000
@@ -6,7 +6,10 @@
 
 PIDGIN_TREE_TOP := ../../..
 include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
-
+CURRENT_REVISION=$(shell \
+		 awk 'BEGIN {"grep node .hg_archival.txt" | getline rev; \
+		 rev=substr(rev,7,6); \
+		 print rev}')
 TARGET = libqq
 TYPE = PLUGIN
 
@@ -16,6 +19,7 @@
 	DLL_INSTALL_DIR =	$(PURPLE_INSTALL_DIR)
 else
 ifeq ($(TYPE),PLUGIN)
+	DEFINES += -DOPENQ_VERSION=\"$(CURRENT_REVISION)\"
 	DLL_INSTALL_DIR =	$(PURPLE_INSTALL_PLUGINS_DIR)
 endif
 endif
@@ -46,16 +50,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 +64,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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/buddy_info.c	Wed Oct 29 21:38:53 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[] = {
+	N_("-"),
+	N_("\xc4\xd0"),
+	N_("\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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/buddy_info.h	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/buddy_list.c	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/buddy_list.h	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/buddy_opt.c	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/buddy_opt.h	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/char_conv.c	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/char_conv.h	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/file_trans.c	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/group.c	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/group.h	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 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	Mon Oct 27 23:13:11 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	Mon Oct 27 23:13:11 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	Mon Oct 27 23:13:11 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	Mon Oct 27 23:13:11 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	Mon Oct 27 23:13:11 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/group_im.c	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/group_im.h	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/group_info.c	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/group_info.h	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/group_internal.c	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/group_internal.h	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/group_join.c	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/group_join.h	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/group_opt.c	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/group_opt.h	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 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	Mon Oct 27 23:13:11 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	Mon Oct 27 23:13:11 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	Mon Oct 27 23:13:11 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/im.c	Wed Oct 29 21:38:53 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 instaed 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/im.h	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/packet_parse.c	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/packet_parse.h	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/qq.c	Wed Oct 29 21:38:53 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 */
@@ -815,7 +1126,7 @@
 	NULL,				/**< ui_info		*/
 	&prpl_info,			/**< extra_info		*/
 	NULL,				/**< prefs_info		*/
-	_qq_actions,
+	qq_actions,
 
 	/* padding */
 	NULL,
@@ -829,35 +1140,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);
 
@@ -876,9 +1212,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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/qq.h	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/qq_base.c	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/qq_base.h	Wed Oct 29 21:38:53 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	Wed Oct 29 21:38:53 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	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/qq_network.c	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/qq_network.h	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/qq_process.c	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/qq_process.h	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/qq_trans.c	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/send_file.c	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 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	Mon Oct 27 23:13:11 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/utils.c	Wed Oct 29 21:38:53 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	Mon Oct 27 23:13:11 2008 +0000
+++ b/libpurple/protocols/qq/utils.h	Wed Oct 29 21:38:53 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/pidgin/gtkdialogs.c	Mon Oct 27 23:13:11 2008 +0000
+++ b/pidgin/gtkdialogs.c	Wed Oct 29 21:38:53 2008 +0000
@@ -70,7 +70,7 @@
 /* Order: Alphabetical by Last Name */
 static const struct developer developers[] = {
 	{"Daniel 'datallah' Atallah",	NULL, NULL},
-	{"John 'rekkanoryo' Bailey",	N_("bug master"), 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},
@@ -80,8 +80,8 @@
 	{"Mark 'KingAnt' Doliner",		NULL, "mark@kingant.net"},
 	{"Sean Egan",					NULL, "sean.egan@gmail.com"},
 	{"Casey Harkins",               NULL,   NULL},
-	{"Gary 'grim' Kramlich",		NULL, NULL},
-	{"Richard 'rlaager' Laager",	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},
@@ -134,7 +134,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"},
@@ -286,6 +286,45 @@
 	{NULL, NULL, 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()
 {
@@ -351,7 +390,6 @@
 	GtkWidget *button;
 	GtkTextIter iter;
 	GString *str;
-	int i;
 	AtkObject *obj;
 	char* filename, *tmp;
 	GdkPixbuf *pixbuf;
@@ -422,119 +460,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, "  <a href=\"mailto:%s\">%s</a>%s%s%s<br/>",
-					developers[i].email, _(developers[i].name),
-					developers[i].role ? " (" : "",
-					developers[i].role ? _(developers[i].role) : "",
-					developers[i].role ? ")" : "");
-		} else {
-			g_string_append_printf(str, "  %s%s%s%s<br/>",
-					_(developers[i].name),
-					developers[i].role ? " (" : "",
-					developers[i].role ? _(developers[i].role) : "",
-					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, "  <a href=\"mailto:%s\">%s</a>%s%s%s<br/>",
-					patch_writers[i].email, _(patch_writers[i].name),
-					patch_writers[i].role ? " (" : "",
-					patch_writers[i].role ? _(patch_writers[i].role) : "",
-					patch_writers[i].role ? ")" : "");
-		} else {
-			g_string_append_printf(str, "  %s%s%s%s<br/>",
-					_(patch_writers[i].name),
-					patch_writers[i].role ? " (" : "",
-					patch_writers[i].role ? _(patch_writers[i].role) : "",
-					patch_writers[i].role ? ")" : "");
-		}
-	}
+	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, "  <a href=\"mailto:%s\">%s</a>%s%s%s<br/>",
-					retired_developers[i].email, _(retired_developers[i].name),
-					retired_developers[i].role ? " (" : "",
-					retired_developers[i].role ? _(retired_developers[i].role) : "",
-					retired_developers[i].role ? ")" : "");
-		} else {
-			g_string_append_printf(str, "  %s%s%s%s<br/>",
-					_(retired_developers[i].name),
-					retired_developers[i].role ? " (" : "",
-					retired_developers[i].role ? _(retired_developers[i].role) : "",
-					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, "  <a href=\"mailto:%s\">%s</a>%s%s%s<br/>",
-					retired_patch_writers[i].email, _(retired_patch_writers[i].name),
-					retired_patch_writers[i].role ? " (" : "",
-					retired_patch_writers[i].role ? _(retired_patch_writers[i].role) : "",
-					retired_patch_writers[i].role ? ")" : "");
-		} else {
-			g_string_append_printf(str, "  %s%s%s%s<br/>",
-					_(retired_patch_writers[i].name),
-					retired_patch_writers[i].role ? " (" : "",
-					retired_patch_writers[i].role ? _(retired_patch_writers[i].role) : "",
-					retired_patch_writers[i].role ? ")" : "");
-		}
-	}
+	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> - <a href=\"mailto:%s\">%s</a><br/>",
-							_(current_translators[i].language),
-							current_translators[i].abbr,
-							current_translators[i].email,
-							_(current_translators[i].name));
-		} 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> - <a href=\"mailto:%s\">%s</a><br/>",
-							_(past_translators[i].language),
-							past_translators[i].abbr,
-							past_translators[i].email,
-							_(past_translators[i].name));
-		} 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"));
--- a/pidgin/gtkimhtml.c	Mon Oct 27 23:13:11 2008 +0000
+++ b/pidgin/gtkimhtml.c	Wed Oct 29 21:38:53 2008 +0000
@@ -1974,7 +1974,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;
 
--- a/po/de.po	Mon Oct 27 23:13:11 2008 +0000
+++ b/po/de.po	Wed Oct 29 21:38:53 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"