changeset 14404:8ff8f1c897b5

[gaim-migrate @ 17112] Fixed chat support. committer: Tailor Script <tailor@pidgin.im>
author Mark Huetsch <markhuetsch>
date Fri, 01 Sep 2006 11:03:18 +0000
parents 646dcf11b4eb
children 8375ecb6152b
files libgaim/protocols/qq/Makefile.am libgaim/protocols/qq/Makefile.mingw libgaim/protocols/qq/buddy_list.c libgaim/protocols/qq/group.c libgaim/protocols/qq/group.h libgaim/protocols/qq/group_conv.c libgaim/protocols/qq/group_find.c libgaim/protocols/qq/group_find.h libgaim/protocols/qq/group_free.c libgaim/protocols/qq/group_free.h libgaim/protocols/qq/group_hash.c libgaim/protocols/qq/group_hash.h libgaim/protocols/qq/group_im.c libgaim/protocols/qq/group_info.c libgaim/protocols/qq/group_info.h libgaim/protocols/qq/group_internal.c libgaim/protocols/qq/group_internal.h libgaim/protocols/qq/group_join.c libgaim/protocols/qq/group_join.h libgaim/protocols/qq/group_network.c libgaim/protocols/qq/group_opt.c libgaim/protocols/qq/group_search.c libgaim/protocols/qq/login_logout.c libgaim/protocols/qq/qq.c libgaim/protocols/qq/qq.h libgaim/protocols/qq/utils.c libgaim/protocols/qq/utils.h
diffstat 27 files changed, 610 insertions(+), 533 deletions(-) [+]
line wrap: on
line diff
--- a/libgaim/protocols/qq/Makefile.am	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/Makefile.am	Fri Sep 01 11:03:18 2006 +0000
@@ -22,15 +22,15 @@
 	crypt.c \
 	crypt.h \
 	group.c \
+	group.h \
 	group_conv.c \
 	group_conv.h \
 	group_find.c \
 	group_find.h \
 	group_free.c \
 	group_free.h \
-	group.h \
-	group_hash.c \
-	group_hash.h \
+	group_internal.c \
+	group_internal.h \
 	group_im.c \
 	group_im.h \
 	group_info.c \
--- a/libgaim/protocols/qq/Makefile.mingw	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/Makefile.mingw	Fri Sep 01 11:03:18 2006 +0000
@@ -54,7 +54,7 @@
 	group_conv.c \
 	group_find.c \
 	group_free.c \
-	group_hash.c \
+	group_internal.c \
 	group_im.c \
 	group_info.c \
 	group_join.c \
--- a/libgaim/protocols/qq/buddy_list.c	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/buddy_list.c	Fri Sep 01 11:03:18 2006 +0000
@@ -37,7 +37,7 @@
 #include "qq.h"	
 #include "group.h"
 #include "group_find.h"
-#include "group_hash.h"
+#include "group_internal.h"
 #include "group_info.h"
 
 #include "qq_proxy.h"
@@ -222,7 +222,7 @@
 			}
 			else {
 				gaim_debug(GAIM_DEBUG_ERROR, "QQ", 
-						"Got an online buddy %d, but not in my buddy list", fe->s->uid);
+						"Got an online buddy %d, but not in my buddy list\n", fe->s->uid);
 			}
 
 			g_free(fe->s->ip);
@@ -231,11 +231,11 @@
 		
 		if(cursor > (data + len)) {
 			 gaim_debug(GAIM_DEBUG_ERROR, "QQ", 
-					"qq_process_get_buddies_online_reply: Dangerous error! maybe protocol changed, notify developers!");
+					"qq_process_get_buddies_online_reply: Dangerous error! maybe protocol changed, notify developers!\n");
 		}
 
 		if (position != QQ_FRIENDS_ONLINE_POSITION_END) {
-			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Has more online buddies, position from %d", position);
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Has more online buddies, position from %d\n", position);
 
 			qq_send_packet_get_buddies_online(gc, position);
 		}
@@ -352,10 +352,6 @@
 	guint32 unknown, position;
 	guint32 uid;
 	guint8 type, groupid;
-
-	qq_buddy *q_bud;
-	gchar *name;
-	GaimBuddy *b;
 	qq_group *group;
 
 	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
@@ -384,41 +380,28 @@
 			read_packet_dw(data, &cursor, len, &uid);
 			/* 04: type 0x1:buddy 0x4:Qun */
 			read_packet_b(data, &cursor, len, &type);
-			/* 05: groupid*4 */
+			/* 05: groupid*4 */ /* seems to always be 0 */
 			read_packet_b(data, &cursor, len, &groupid);
-			groupid >>= 2;   /* these 2 bits might not be 0, faint! */
+			/*
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "groupid: %i\n", groupid);
+			groupid >>= 2;
+			*/
 			if (uid == 0 || (type != 0x1 && type != 0x4)) {
-				gaim_debug(GAIM_DEBUG_WARNING, "QQ",
+				gaim_debug(GAIM_DEBUG_INFO, "QQ",
 					   "Buddy entry, uid=%d, type=%d", uid, type);
 				continue;
 			} 
 			if(0x1 == type) { /* a buddy */
-				name = uid_to_gaim_name(uid);
-				b = gaim_find_buddy(gc->account, name);
-				g_free(name);
-
-				if (b == NULL) {
-					b = qq_add_buddy_by_recv_packet(gc, uid, TRUE, TRUE);
-					q_bud = b->proto_data;
-				}
-				else {
-					q_bud = NULL;
-					b->proto_data = q_bud;	/* wrong !!!! */
-				}
-				qd->buddies = g_list_append(qd->buddies, q_bud);
-				qq_update_buddy_contact(gc, q_bud);
+				/* don't do anything but count - buddies are handled by 
+				 * qq_send_packet_get_buddies_list */
 				++i;
 			} else { /* a group */
-				group = qq_group_find_by_internal_group_id(gc, uid);
+				group = qq_group_find_by_id(gc, uid, QQ_INTERNAL_ID);
 				if(group == NULL) {
-					/*XXX not working
-					group = qq_group_create_by_id(gc, uid, 0);
+					qq_set_pending_id(&qd->adding_groups_from_server, uid, TRUE);
+					group = g_newa(qq_group, 1);
+					group->internal_group_id = uid;
 					qq_send_cmd_group_get_group_info(gc, group);
-					*/
-					gaim_debug(GAIM_DEBUG_ERROR, "QQ", 
-							"Get a Qun with internal group %d\n", uid);
-					gaim_notify_info(gc, _("QQ Qun Operation"), 
-							_("Find one Qun in the server list, but i don't know its external id, please re-rejoin it manually"), NULL);
 				} else {
 					group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER;
 					qq_group_refresh(gc, group);
--- a/libgaim/protocols/qq/group.c	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/group.c	Fri Sep 01 11:03:18 2006 +0000
@@ -26,7 +26,7 @@
 #include "prpl.h"
 #include "request.h"
 
-#include "group_hash.h"
+#include "group_internal.h"
 #include "group_info.h"
 #include "group_search.h"
 #include "utils.h"
@@ -43,6 +43,15 @@
 	qq_send_cmd_group_search_group(gc, external_group_id);
 }
 
+static void _qq_group_search_cancel_callback(GaimConnection *gc, const gchar *input)
+{
+	qq_data *qd;
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	gaim_roomlist_set_in_progress(qd->roomlist, FALSE);
+}
+
 /* This is needed for GaimChat node to be valid */
 GList *qq_chat_info(GaimConnection *gc)
 {
@@ -55,18 +64,20 @@
 	pce->label = _("ID: ");
 	pce->identifier = QQ_GROUP_KEY_EXTERNAL_ID;
 	m = g_list_append(m, pce);
-
-	pce = g_new0(struct proto_chat_entry, 1);
-	pce->label = _("Admin: ");
-	pce->identifier = QQ_GROUP_KEY_CREATOR_UID;
-	m = g_list_append(m, pce);
+	
+	return m;
+}
 
-	pce = g_new0(struct proto_chat_entry, 1);
-	pce->label = _("Status: ");
-	pce->identifier = QQ_GROUP_KEY_MEMBER_STATUS_DESC;
-	m = g_list_append(m, pce);
+GHashTable *qq_chat_info_defaults(GaimConnection *gc, const gchar *chat_name)
+{
+	GHashTable *defaults;
 
-	return m;
+	defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
+
+	if (chat_name != NULL)
+		g_hash_table_insert(defaults, QQ_GROUP_KEY_EXTERNAL_ID, g_strdup(chat_name));
+
+	return defaults;
 }
 
 /*  get a list of qq groups */
@@ -107,9 +118,11 @@
 
 	gaim_request_input(gc, _("QQ Qun"),
 			   _("Please input external group ID"),
-			   _("You can only search for permanent QQ group\nInput 0 or leave it blank to search for demo groups"),
-			   NULL, FALSE, FALSE, NULL, _("Search"),
-			   G_CALLBACK(_qq_group_search_callback), _("Cancel"), NULL, gc);
+			   _("You can only search for permanent QQ groups\n"),
+			   NULL, FALSE, FALSE, NULL, 
+			   _("Search"), G_CALLBACK(_qq_group_search_callback), 
+			   _("Cancel"), G_CALLBACK(_qq_group_search_cancel_callback), 
+			   gc);
 
 	return qd->roomlist;
 }
--- a/libgaim/protocols/qq/group.h	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/group.h	Fri Sep 01 11:03:18 2006 +0000
@@ -39,7 +39,7 @@
 } qq_group_member_status;
 
 typedef struct _qq_group {
-	/* all these will be saved when exit GAIM */
+	/* all these will be saved when we exit Gaim */
 	qq_group_member_status my_status;	/* my status for this group */
 	gchar *my_status_desc;			/* my status description */
 	guint32 internal_group_id;
@@ -50,12 +50,13 @@
 	guint8 auth_type;
 	gchar *group_name_utf8;
 	gchar *group_desc_utf8;
-	/* all these will loaded from network only */
+	/* all these will be loaded from the network */
 	gchar *notice_utf8;	/* group notice by admin */
-	GList *members;		/* those evert appear in the group */
+	GList *members;	
 } qq_group;
 
 GList *qq_chat_info(GaimConnection *gc);
+GHashTable *qq_chat_info_defaults(GaimConnection *gc, const gchar *chat_name);
 
 void qq_group_init(GaimConnection *gc);
 
--- a/libgaim/protocols/qq/group_conv.c	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/group_conv.c	Fri Sep 01 11:03:18 2006 +0000
@@ -37,7 +37,8 @@
 	g_return_if_fail(gc != NULL && gc->proto_data != NULL && group != NULL);
 	qd = (qq_data *) gc->proto_data;
 
-	conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, group->group_name_utf8, gaim_connection_get_account(gc));
+	conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, 
+			group->group_name_utf8, gaim_connection_get_account(gc));
 	if (conv == NULL)	/* show only one window per group */
 		serv_got_joined_chat(gc, qd->channel++, group->group_name_utf8);
 }
@@ -54,17 +55,19 @@
 
 	names = NULL;
 	flags = NULL;
-	conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, group->group_name_utf8, gaim_connection_get_account(gc));
+	conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, 
+			group->group_name_utf8, gaim_connection_get_account(gc));
 	if (conv != NULL && group->members != NULL) {
 		list = group->members;
 		while (list != NULL) {
 			member = (qq_buddy *) list->data;
 			/* always put it even offline */
 			names = g_list_append(names,
-					      (member->nickname !=
-					      NULL) ?
-					      g_strdup(member->nickname) : uid_to_gaim_name(member->uid));
-		
+					/* 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->nickname != NULL && *(member->nickname) != '\0') ?
+					g_strdup_printf("%s (qq-%u)", member->nickname, 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 |= (GAIM_CBFLAGS_TYPING | GAIM_CBFLAGS_VOICE);
--- a/libgaim/protocols/qq/group_find.c	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/group_find.c	Fri Sep 01 11:03:18 2006 +0000
@@ -29,38 +29,6 @@
 #include "qq.h"
 #include "utils.h"
 
-/* find a chat member's valid gaim_name of its nickname and chat room channel */
-gchar *qq_group_find_member_by_channel_and_nickname(GaimConnection *gc, gint channel, const gchar *who) 
-{
-	qq_group *group;
-	qq_buddy *member;
-	GList *list;
-
-	g_return_val_if_fail(gc != NULL && who != NULL, NULL);
-
-	/* TODO checkbox for this in UI */
-	/* if it starts with QQ_NAME_PREFIX, we think it is valid name already
-	 * otherwise we think it is nickname and try to find the matching gaim_name */
-	/*
-	if (gaim_str_has_prefix(who, QQ_NAME_PREFIX) && gaim_name_to_uid(who) > 0)
-		return (gchar *) who;
-		*/
-
-	group = qq_group_find_by_channel(gc, channel);
-	g_return_val_if_fail(group != NULL, NULL);
-
-	list = group->members;
-	member = NULL;
-	while (list != NULL) {
-		member = (qq_buddy *) list->data;
-		if (member->nickname != NULL && !g_ascii_strcasecmp(member->nickname, who))
-			break;
-		list = list->next;
-	}
-
-	return (member == NULL) ? NULL : uid_to_gaim_name(member->uid);
-}
-
 /* find the internal_group_id by the reply packet sequence
  * return TRUE if we have a record of it, return FALSE if not */
 gboolean qq_group_find_internal_group_id_by_seq(GaimConnection *gc, guint16 seq, guint32 *internal_group_id)
@@ -69,7 +37,10 @@
 	qq_data *qd;
 	group_packet *p;
 
-	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL && internal_group_id != NULL, FALSE);
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, FALSE);
+
+	if (internal_group_id == NULL)
+		return FALSE;
 	qd = (qq_data *) gc->proto_data;
 
 	list = qd->group_packets;
@@ -87,7 +58,7 @@
 	return FALSE;
 }
 
-/* find a qq_buddy by uid, called by qq_im.c */
+/* 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;
@@ -138,7 +109,7 @@
 		buddy = gaim_find_buddy(gaim_connection_get_account(gc), uid_to_gaim_name(member_uid));
 		if (buddy != NULL) {
 			q_bud = (qq_buddy *) buddy->proto_data;
-			if (q_bud != NULL)
+			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);
@@ -175,23 +146,24 @@
 	return group;
 }
 
-/* find a qq_group by internal_group_id */
-qq_group *qq_group_find_by_internal_group_id(GaimConnection *gc, guint32 internal_group_id)
+/* find a qq_group by its id, flag is QQ_INTERNAL_ID or QQ_EXTERNAL_ID */
+qq_group *qq_group_find_by_id(GaimConnection *gc, guint32 id, gboolean flag)
 {
 	GList *list;
 	qq_group *group;
 	qq_data *qd;
 
-	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL && internal_group_id > 0, NULL);
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, NULL);
+	qd = (qq_data *) gc->proto_data;
 
-	qd = (qq_data *) gc->proto_data;
-	if (qd->groups == NULL)
+	if (qd->groups == NULL || id <= 0)
 		return NULL;
 
 	list = qd->groups;
 	while (list != NULL) {
 		group = (qq_group *) list->data;
-		if (group->internal_group_id == internal_group_id)
+		if (flag == QQ_INTERNAL_ID ? 
+				(group->internal_group_id == id) : (group->external_group_id == id))
 			return group;
 		list = list->next;
 	}
--- a/libgaim/protocols/qq/group_find.h	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/group_find.h	Fri Sep 01 11:03:18 2006 +0000
@@ -27,12 +27,14 @@
 #include "connection.h"
 #include "group.h"
 
-gchar *qq_group_find_member_by_channel_and_nickname(GaimConnection *gc, gint channel, const gchar *who);
+#define QQ_INTERNAL_ID 0
+#define QQ_EXTERNAL_ID 1
+
 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(GaimConnection *gc, qq_group *group, guint32 member_uid);
 gboolean qq_group_find_internal_group_id_by_seq(GaimConnection *gc, guint16 seq, guint32 *internal_group_id);
 qq_group *qq_group_find_by_channel(GaimConnection *gc, gint channel);
-qq_group *qq_group_find_by_internal_group_id(GaimConnection *gc, guint32 internal_group_id);
+qq_group *qq_group_find_by_id(GaimConnection *gc, guint32 id, gboolean flag);
 
 #endif
--- a/libgaim/protocols/qq/group_free.c	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/group_free.c	Fri Sep 01 11:03:18 2006 +0000
@@ -23,12 +23,11 @@
 #include "debug.h"
 
 #include "buddy_status.h"
-#include "group.h"
 #include "group_free.h"
 #include "group_network.h"
 
 /* gracefully free all members in a group */
-static void _qq_group_free_member(qq_group *group)
+static void qq_group_free_member(qq_group *group)
 {
 	gint i;
 	GList *list;
@@ -48,10 +47,10 @@
 }
 
 /* gracefully free the memory for one qq_group */
-static void _qq_group_free(qq_group *group)
+void qq_group_free(qq_group *group)
 {
 	g_return_if_fail(group != NULL);
-	_qq_group_free_member(group);
+	qq_group_free_member(group);
 	g_free(group->group_name_utf8);
 	g_free(group->group_desc_utf8);
 	g_free(group);
@@ -73,25 +72,6 @@
 	gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d group packets are freed!\n", i);
 }
 
-void qq_group_remove_by_internal_group_id(qq_data *qd, guint32 internal_group_id)
-{
-	qq_group *group;
-	GList *list;
-	g_return_if_fail(qd != NULL);
-
-	list = qd->groups;
-	while (list != NULL) {
-		group = (qq_group *) qd->groups->data;
-		if (internal_group_id == group->internal_group_id) {
-			qd->groups = g_list_remove(qd->groups, group);
-			_qq_group_free(group);
-			break;
-		} else {
-			list = list->next;
-		}
-	}
-}
-
 void qq_group_free_all(qq_data *qd)
 {
 	qq_group *group;
@@ -103,7 +83,7 @@
 		i++;
 		group = (qq_group *) qd->groups->data;
 		qd->groups = g_list_remove(qd->groups, group);
-		_qq_group_free(group);
+		qq_group_free(group);
 	}
 
 	gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d groups are freed\n", i);
--- a/libgaim/protocols/qq/group_free.h	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/group_free.h	Fri Sep 01 11:03:18 2006 +0000
@@ -25,11 +25,11 @@
 
 #include <glib.h>
 #include "qq.h"
+#include "group.h"
 
 void qq_group_packets_free(qq_data *qd);
 
+void qq_group_free(qq_group *group);
 void qq_group_free_all(qq_data *qd);
 
-void qq_group_remove_by_internal_group_id(qq_data *qd, guint32 internal_group_id);
-
 #endif
--- a/libgaim/protocols/qq/group_hash.c	Fri Sep 01 10:05:30 2006 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,195 +0,0 @@
-/**
-* The QQ2003C protocol plugin
- *
- * for gaim
- *
- * Copyright (C) 2004 Puzzlebird
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-#include "blist.h"
-#include "debug.h"
-
-#include "buddy_opt.h"
-#include "group_hash.h"
-#include "group_misc.h"
-#include "utils.h"
-
-static gchar *_qq_group_set_my_status_desc(qq_group *group)
-{
-	const char *status_desc;
-	g_return_val_if_fail(group != NULL, g_strdup(""));
-
-	switch (group->my_status) {
-	case QQ_GROUP_MEMBER_STATUS_NOT_MEMBER:
-		status_desc = _("I am not member");
-		break;
-	case QQ_GROUP_MEMBER_STATUS_IS_MEMBER:
-		status_desc = _("I am a member");
-		break;
-	case QQ_GROUP_MEMBER_STATUS_APPLYING:
-		status_desc = _("I am applying to join");
-		break;
-	case QQ_GROUP_MEMBER_STATUS_IS_ADMIN:
-		status_desc = _("I am the admin");
-		break;
-	default:
-		status_desc = _("Unknown status");
-	}
-
-	return g_strdup(status_desc);
-}
-
-static void _qq_group_add_to_blist(GaimConnection *gc, qq_group *group)
-{
-	GHashTable *components;
-	GaimGroup *g;
-	GaimChat *chat;
-	components = qq_group_to_hashtable(group);
-	chat = gaim_chat_new(gaim_connection_get_account(gc), group->group_name_utf8, components);
-	g = qq_get_gaim_group(GAIM_GROUP_QQ_QUN);
-	gaim_blist_add_chat(chat, g, NULL);
-	gaim_debug(GAIM_DEBUG_INFO, "QQ", "You have add group \"%s\" to blist locally\n", group->group_name_utf8);
-}
-
-/* create a dummy qq_group, which includes only internal_id and external_id
- * all other attributes should be set to empty.
- * and we need to send a get_group_info to QQ server to update it right away */
-qq_group *qq_group_create_by_id(GaimConnection *gc, guint32 internal_id, guint32 external_id)
-{
-	qq_group *group;
-	qq_data *qd;
-
-	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, NULL);
-	g_return_val_if_fail(internal_id > 0, NULL);
-	qd = (qq_data *) gc->proto_data;
-
-	group = g_new0(qq_group, 1);
-	group->my_status = QQ_GROUP_MEMBER_STATUS_NOT_MEMBER;
-	group->my_status_desc = _qq_group_set_my_status_desc(group);
-	group->internal_group_id = internal_id;
-	group->external_group_id = external_id;
-	group->group_type = 0x01;	/* assume permanent Qun */
-	group->creator_uid = 10000;	/* assume by QQ admin */
-	group->group_category = 0x01;
-	group->auth_type = 0x02;	/* assume need auth */
-	group->group_name_utf8 = g_strdup("");
-	group->group_desc_utf8 = g_strdup("");
-	group->notice_utf8 = g_strdup("");
-	group->members = NULL;
-
-	qd->groups = g_list_append(qd->groups, group);
-	_qq_group_add_to_blist(gc, group);
-
-	return group;
-}
-
-/* convert a qq_group to hash-table, which could be component of GaimChat */
-GHashTable *qq_group_to_hashtable(qq_group *group)
-{
-	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_GROUP_KEY_MEMBER_STATUS), g_strdup_printf("%d", group->my_status));
-	group->my_status_desc = _qq_group_set_my_status_desc(group);
-
-	g_hash_table_insert(components,
-			    g_strdup(QQ_GROUP_KEY_INTERNAL_ID), g_strdup_printf("%d", group->internal_group_id));
-	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_EXTERNAL_ID),
-			    g_strdup_printf("%d", group->external_group_id));
-	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_GROUP_TYPE), g_strdup_printf("%d", group->group_type));
-	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_CREATOR_UID), g_strdup_printf("%d", group->creator_uid));
-	g_hash_table_insert(components,
-			    g_strdup(QQ_GROUP_KEY_GROUP_CATEGORY), g_strdup_printf("%d", group->group_category));
-	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_AUTH_TYPE), g_strdup_printf("%d", group->auth_type));
-	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_MEMBER_STATUS_DESC), g_strdup(group->my_status_desc));
-	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_GROUP_NAME_UTF8), g_strdup(group->group_name_utf8));
-	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_GROUP_DESC_UTF8), g_strdup(group->group_desc_utf8));
-	return components;
-}
-
-/* create a qq_group from hashtable */
-qq_group *qq_group_from_hashtable(GaimConnection *gc, GHashTable *data)
-{
-	qq_data *qd;
-	qq_group *group;
-
-	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, NULL);
-	g_return_val_if_fail(data != NULL, NULL);
-	qd = (qq_data *) gc->proto_data;
-
-	group = g_new0(qq_group, 1);
-	group->my_status =
-	    qq_string_to_dec_value
-	    (NULL ==
-	     g_hash_table_lookup(data,
-				 QQ_GROUP_KEY_MEMBER_STATUS) ?
-	     g_strdup_printf("%d",
-			     QQ_GROUP_MEMBER_STATUS_NOT_MEMBER) :
-	     g_hash_table_lookup(data, QQ_GROUP_KEY_MEMBER_STATUS));
-	group->internal_group_id = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_INTERNAL_ID));
-	group->external_group_id = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_EXTERNAL_ID));
-	group->group_type = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_GROUP_TYPE));
-	group->creator_uid = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_CREATOR_UID));
-	group->group_category = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_GROUP_CATEGORY));
-	group->auth_type = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_AUTH_TYPE));
-	group->group_name_utf8 = g_strdup(g_hash_table_lookup(data, QQ_GROUP_KEY_GROUP_NAME_UTF8));
-	group->group_desc_utf8 = g_strdup(g_hash_table_lookup(data, QQ_GROUP_KEY_GROUP_DESC_UTF8));
-	group->my_status_desc = _qq_group_set_my_status_desc(group);
-
-	qd->groups = g_list_append(qd->groups, group);
-
-	return group;
-}
-
-/* refresh group local subscription */
-void qq_group_refresh(GaimConnection *gc, qq_group *group)
-{
-	GaimChat *chat;
-	g_return_if_fail(gc != NULL && group != NULL);
-
-	chat = gaim_blist_find_chat(gaim_connection_get_account(gc), g_strdup_printf("%d", group->external_group_id));
-	if (chat == NULL && group->my_status != QQ_GROUP_MEMBER_STATUS_NOT_MEMBER) {
-		_qq_group_add_to_blist(gc, group);
-	} else if (chat != NULL) {	/* we have a local record, update its info */
-		/* if there is group_name_utf8, we update the group name */
-		if (group->group_name_utf8 != NULL && strlen(group->group_name_utf8) > 0)
-			gaim_blist_alias_chat(chat, group->group_name_utf8);
-		g_hash_table_replace(chat->components,
-				     g_strdup(QQ_GROUP_KEY_MEMBER_STATUS), g_strdup_printf("%d", group->my_status));
-		group->my_status_desc = _qq_group_set_my_status_desc(group);
-		g_hash_table_replace(chat->components,
-				     g_strdup(QQ_GROUP_KEY_MEMBER_STATUS_DESC), g_strdup(group->my_status_desc));
-		g_hash_table_replace(chat->components,
-				     g_strdup(QQ_GROUP_KEY_INTERNAL_ID),
-				     g_strdup_printf("%d", group->internal_group_id));
-		g_hash_table_replace(chat->components,
-				     g_strdup(QQ_GROUP_KEY_EXTERNAL_ID),
-				     g_strdup_printf("%d", group->external_group_id));
-		g_hash_table_replace(chat->components,
-				     g_strdup(QQ_GROUP_KEY_GROUP_TYPE), g_strdup_printf("%d", group->group_type));
-		g_hash_table_replace(chat->components,
-				     g_strdup(QQ_GROUP_KEY_CREATOR_UID), g_strdup_printf("%d", group->creator_uid));
-		g_hash_table_replace(chat->components,
-				     g_strdup(QQ_GROUP_KEY_GROUP_CATEGORY),
-				     g_strdup_printf("%d", group->group_category));
-		g_hash_table_replace(chat->components,
-				     g_strdup(QQ_GROUP_KEY_AUTH_TYPE), g_strdup_printf("%d", group->auth_type));
-		g_hash_table_replace(chat->components,
-				     g_strdup(QQ_GROUP_KEY_GROUP_NAME_UTF8), g_strdup(group->group_name_utf8));
-		g_hash_table_replace(chat->components,
-				     g_strdup(QQ_GROUP_KEY_GROUP_DESC_UTF8), g_strdup(group->group_desc_utf8));
-	}
-}
--- a/libgaim/protocols/qq/group_hash.h	Fri Sep 01 10:05:30 2006 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-/**
-* The QQ2003C protocol plugin
- *
- * for gaim
- *
- * Copyright (C) 2004 Puzzlebird
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-#ifndef _QQ_GROUP_HASH_H_
-#define _QQ_GROUP_HASH_H_
-
-#include <glib.h>
-#include "group.h"
-
-#define QQ_GROUP_KEY_MEMBER_STATUS      "my_status_code"
-#define QQ_GROUP_KEY_MEMBER_STATUS_DESC "my_status_desc"
-#define QQ_GROUP_KEY_INTERNAL_ID        "internal_group_id"
-#define QQ_GROUP_KEY_EXTERNAL_ID        "external_group_id"
-#define QQ_GROUP_KEY_GROUP_TYPE         "group_type"
-#define QQ_GROUP_KEY_CREATOR_UID        "creator_uid"
-#define QQ_GROUP_KEY_GROUP_CATEGORY     "group_category"
-#define QQ_GROUP_KEY_AUTH_TYPE          "auth_type"
-#define QQ_GROUP_KEY_GROUP_NAME_UTF8    "group_name_utf8"
-#define QQ_GROUP_KEY_GROUP_DESC_UTF8    "group_desc_utf8"
-
-qq_group *qq_group_create_by_id(GaimConnection *gc, guint32 internal_id, guint32 external_id);
-GHashTable *qq_group_to_hashtable(qq_group *group);
-
-qq_group *qq_group_from_hashtable(GaimConnection *gc, GHashTable *data);
-void qq_group_refresh(GaimConnection *gc, qq_group *group);
-
-#endif
--- a/libgaim/protocols/qq/group_im.c	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/group_im.c	Fri Sep 01 11:03:18 2006 +0000
@@ -29,7 +29,7 @@
 
 #include "char_conv.h"
 #include "group_find.h"
-#include "group_hash.h"
+#include "group_internal.h"
 #include "group_info.h"
 #include "group_im.h"
 #include "group_network.h"
@@ -171,7 +171,7 @@
 
 	gaim_notify_warning(gc, _("QQ Qun Operation"), msg, reason);
 
-	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 	if (group != NULL) {
 		group->my_status = QQ_GROUP_MEMBER_STATUS_NOT_MEMBER;
 		qq_group_refresh(gc, group);
@@ -211,7 +211,7 @@
 
 	gaim_notify_warning(gc, _("QQ Qun Operation"), msg, NULL);
 
-	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 	if (group != NULL) {
 		group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER;
 		qq_group_refresh(gc, group);
@@ -246,7 +246,7 @@
 	msg = g_strdup_printf(_("You [%d] has exit group \"%d\""), uid, external_group_id);
 	gaim_notify_info(gc, _("QQ Qun Operation"), msg, NULL);
 
-	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 	if (group != NULL) {
 		group->my_status = QQ_GROUP_MEMBER_STATUS_NOT_MEMBER;
 		qq_group_refresh(gc, group);
@@ -278,14 +278,14 @@
 	g_return_if_fail(external_group_id > 0 && uid > 0);
 
 	msg = g_strdup_printf(_("You [%d] has been added by group \"%d\""), uid, external_group_id);
-	gaim_notify_info(gc, _("QQ Qun Operation"), msg, _("OpenQ has added this group to your buddy list"));
+	gaim_notify_info(gc, _("QQ Qun Operation"), msg, _("This group has been added to your buddy list"));
 
-	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 	if (group != NULL) {
 		group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER;
 		qq_group_refresh(gc, group);
 	} else {		/* no such group, try to create a dummy first, and then update */
-		group = qq_group_create_by_id(gc, internal_group_id, external_group_id);
+		group = qq_group_create_internal_record(gc, internal_group_id, external_group_id, NULL);
 		group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER;
 		qq_group_refresh(gc, group);
 		qq_send_cmd_group_get_group_info(gc, group);
@@ -379,7 +379,7 @@
 	else
 		msg_utf8_encoded = qq_to_utf8(msg_with_gaim_smiley, QQ_CHARSET_DEFAULT);
 
-	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 	g_return_if_fail(group != NULL);
 
 	conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, group->group_name_utf8, gaim_connection_get_account(gc));
--- a/libgaim/protocols/qq/group_info.c	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/group_info.c	Fri Sep 01 11:03:18 2006 +0000
@@ -26,7 +26,7 @@
 #include "buddy_status.h"
 #include "char_conv.h"
 #include "group_find.h"
-#include "group_hash.h"
+#include "group_internal.h"
 #include "group_info.h"
 #include "buddy_status.h"
 #include "group_network.h"
@@ -42,7 +42,7 @@
 	    (time(NULL) - member->last_refresh) > QQ_GROUP_CHAT_REFRESH_NICKNAME_INTERNAL;
 }
 
-/* this is done when we receive the reply to get_online_member sub_cmd
+/* 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 _qq_group_set_members_all_offline(qq_group *group)
 {
@@ -82,7 +82,7 @@
 }
 
 /* send packet to get online group member, called by keep_alive */
-void qq_send_cmd_group_get_online_member(GaimConnection *gc, qq_group *group)
+void qq_send_cmd_group_get_online_members(GaimConnection *gc, qq_group *group)
 {
 	guint8 *raw_data, *cursor;
 	gint bytes, data_len;
@@ -111,8 +111,8 @@
 		qq_send_group_cmd(gc, group, raw_data, data_len);
 }
 
-/* send packet to get group member info */
-void qq_send_cmd_group_get_member_info(GaimConnection *gc, qq_group *group)
+/* send packet to get info for each group member */
+void qq_send_cmd_group_get_members_info(GaimConnection *gc, qq_group *group)
 {
 	guint8 *raw_data, *cursor;
 	gint bytes, data_len, i;
@@ -160,9 +160,10 @@
 	qq_buddy *member;
 	qq_data *qd;
 	GaimConversation *gaim_conv;
-	guint8 orgnization, role;
-	guint16 unknown;
-	guint32 member_uid, internal_group_id;
+	guint8 organization, role;
+	guint16 unknown, max_members;
+	guint32 member_uid, internal_group_id, external_group_id;
+	GSList *pending_id;
 	gint pascal_len, i;
 	guint32 unknown4;
 	guint8 unknown1;
@@ -173,11 +174,18 @@
 
 	read_packet_dw(data, cursor, len, &(internal_group_id));
 	g_return_if_fail(internal_group_id > 0);
+	read_packet_dw(data, cursor, len, &(external_group_id));
+	g_return_if_fail(internal_group_id > 0);
 
-	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	pending_id = qq_get_pending_id(qd->adding_groups_from_server, internal_group_id);
+	if (pending_id != NULL) {
+		qq_set_pending_id(&qd->adding_groups_from_server, internal_group_id, FALSE);
+		qq_group_create_internal_record(gc, internal_group_id, external_group_id, NULL);
+	}
+
+	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 	g_return_if_fail(group != NULL);
 
-	read_packet_dw(data, cursor, len, &(group->external_group_id));
 	read_packet_b(data, cursor, len, &(group->group_type));
 	read_packet_dw(data, cursor, len, &unknown4);	/* unknown 4 bytes */
 	read_packet_dw(data, cursor, len, &(group->creator_uid));
@@ -185,7 +193,7 @@
 	read_packet_dw(data, cursor, len, &unknown4);	/* oldCategory */
 	read_packet_w(data, cursor, len, &unknown);	
 	read_packet_dw(data, cursor, len, &(group->group_category));
-	read_packet_w(data, cursor, len, &(unknown));	/* 0x0000 */
+	read_packet_w(data, cursor, len, &max_members);
 	read_packet_b(data, cursor, len, &unknown1);
 	read_packet_dw(data, cursor, len, &(unknown4));	/* versionID */
 
@@ -202,17 +210,18 @@
 	while (*cursor < data + len) {
 		read_packet_dw(data, cursor, len, &member_uid);
 		i++;
-		read_packet_b(data, cursor, len, &orgnization);
+		read_packet_b(data, cursor, len, &organization);
 		read_packet_b(data, cursor, len, &role);
 
-		if(orgnization != 0 || role != 0) {
-			gaim_debug(GAIM_DEBUG_INFO, "QQ", "group member %d: orgnizatio=%d, role=%d\n", member_uid, orgnization, role);
+		if(organization != 0 || role != 0) {
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "group member %d: organization=%d, role=%d\n", member_uid, organization, role);
 		}
 		member = qq_group_find_or_add_member(gc, group, member_uid);
-		member->role = role;
+		if (member != NULL)
+			member->role = role;
 	}
         if(*cursor > (data + len)) {
-                         gaim_debug(GAIM_DEBUG_ERROR, "QQ", "group_cmd_get_group_info: Dangerous error! maybe protocal changed, notify me!");
+                         gaim_debug(GAIM_DEBUG_ERROR, "QQ", "group_cmd_get_group_info: Dangerous error! maybe protocol changed, notify me!");
         }
 
 	gaim_debug(GAIM_DEBUG_INFO, "QQ", "group \"%s\" has %d members\n", group->group_name_utf8, i);
@@ -233,7 +242,7 @@
 	}
 }
 
-void qq_process_group_cmd_get_online_member(guint8 *data, guint8 **cursor, gint len, GaimConnection *gc)
+void qq_process_group_cmd_get_online_members(guint8 *data, guint8 **cursor, gint len, GaimConnection *gc)
 {
 	guint32 internal_group_id, member_uid;
 	guint8 unknown;
@@ -254,7 +263,7 @@
 	bytes += read_packet_b(data, cursor, len, &unknown);	/* 0x3c ?? */
 	g_return_if_fail(internal_group_id > 0);
 
-	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 	if (group == NULL) {
 		gaim_debug(GAIM_DEBUG_ERROR, "QQ", 
 				"We have no group info for internal id [%d]\n", internal_group_id);
@@ -272,14 +281,14 @@
 	}
         if(*cursor > (data + len)) {
                          gaim_debug(GAIM_DEBUG_ERROR, "QQ", 
-					 "group_cmd_get_online_member: Dangerous error! maybe protocol changed, notify developers!");
+					 "group_cmd_get_online_members: Dangerous error! maybe protocol changed, notify developers!");
         }
 
 	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Group \"%s\" has %d online members\n", group->group_name_utf8, i);
 }
 
-/* process the reply to get_member_info packet */
-void qq_process_group_cmd_get_member_info(guint8 *data, guint8 **cursor, gint len, GaimConnection *gc)
+/* process the reply to get_members_info packet */
+void qq_process_group_cmd_get_members_info(guint8 *data, guint8 **cursor, gint len, GaimConnection *gc)
 {
 	guint32 internal_group_id, member_uid;
 	guint16 unknown;
@@ -292,10 +301,13 @@
 	read_packet_dw(data, cursor, len, &internal_group_id);
 	g_return_if_fail(internal_group_id > 0);
 
-	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 	g_return_if_fail(group != NULL);
 
 	i = 0;
+	/* TODO: Something is off. I get an entry with strange values 
+	 * (including a nick of "") buried in here. I need to find more 
+	 * groups to join before I can figure this out */
 	/* now starts the member info, as get buddy list reply */
 	while (*cursor < data + len) {
 		read_packet_dw(data, cursor, len, &member_uid);
@@ -317,7 +329,7 @@
 	}
         if(*cursor > (data + len)) {
                          gaim_debug(GAIM_DEBUG_ERROR, "QQ", 
-					 "group_cmd_get_member_info: Dangerous error! maybe protocol changed, notify developers!");
+					 "group_cmd_get_members_info: Dangerous error! maybe protocol changed, notify developers!");
         }
 	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Group \"%s\" obtained %d member info\n", group->group_name_utf8, i);
 }
--- a/libgaim/protocols/qq/group_info.h	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/group_info.h	Fri Sep 01 11:03:18 2006 +0000
@@ -28,10 +28,10 @@
 #include "group.h"
 
 void qq_send_cmd_group_get_group_info(GaimConnection *gc, qq_group *group);
-void qq_send_cmd_group_get_online_member(GaimConnection *gc, qq_group *group);
-void qq_send_cmd_group_get_member_info(GaimConnection *gc, qq_group *group);
+void qq_send_cmd_group_get_online_members(GaimConnection *gc, qq_group *group);
+void qq_send_cmd_group_get_members_info(GaimConnection *gc, qq_group *group);
 void qq_process_group_cmd_get_group_info(guint8 *data, guint8 **cursor, gint len, GaimConnection *gc);
-void qq_process_group_cmd_get_online_member(guint8 *data, guint8 **cursor, gint len, GaimConnection *gc);
-void qq_process_group_cmd_get_member_info(guint8 *data, guint8 **cursor, gint len, GaimConnection *gc);
+void qq_process_group_cmd_get_online_members(guint8 *data, guint8 **cursor, gint len, GaimConnection *gc);
+void qq_process_group_cmd_get_members_info(guint8 *data, guint8 **cursor, gint len, GaimConnection *gc);
 
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgaim/protocols/qq/group_internal.c	Fri Sep 01 11:03:18 2006 +0000
@@ -0,0 +1,238 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "blist.h"
+#include "debug.h"
+
+#include "buddy_opt.h"
+#include "group_free.h"
+#include "group_internal.h"
+#include "group_misc.h"
+#include "utils.h"
+
+static gchar *_qq_group_set_my_status_desc(qq_group *group)
+{
+	const char *status_desc;
+	g_return_val_if_fail(group != NULL, g_strdup(""));
+
+	switch (group->my_status) {
+	case QQ_GROUP_MEMBER_STATUS_NOT_MEMBER:
+		status_desc = _("I am not member");
+		break;
+	case QQ_GROUP_MEMBER_STATUS_IS_MEMBER:
+		status_desc = _("I am a member");
+		break;
+	case QQ_GROUP_MEMBER_STATUS_APPLYING:
+		status_desc = _("I am applying to join");
+		break;
+	case QQ_GROUP_MEMBER_STATUS_IS_ADMIN:
+		status_desc = _("I am the admin");
+		break;
+	default:
+		status_desc = _("Unknown status");
+	}
+
+	return g_strdup(status_desc);
+}
+
+static void _qq_group_add_to_blist(GaimConnection *gc, qq_group *group)
+{
+	GHashTable *components;
+	GaimGroup *g;
+	GaimChat *chat;
+	components = qq_group_to_hashtable(group);
+	chat = gaim_chat_new(gaim_connection_get_account(gc), group->group_name_utf8, components);
+	g = qq_get_gaim_group(GAIM_GROUP_QQ_QUN);
+	gaim_blist_add_chat(chat, g, NULL);
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "You have added group \"%s\" to blist locally\n", group->group_name_utf8);
+}
+
+/* Create a dummy qq_group, which includes only internal_id, external_id,
+ * and potentially group_name_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(GaimConnection *gc,
+                guint32 internal_id, guint32 external_id, gchar *group_name_utf8)
+{
+        qq_group *group;
+        qq_data *qd;
+
+        g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, NULL);
+        g_return_val_if_fail(internal_id > 0, NULL);
+        qd = (qq_data *) gc->proto_data;
+
+        group = g_new0(qq_group, 1);
+        group->my_status = QQ_GROUP_MEMBER_STATUS_NOT_MEMBER;
+        group->my_status_desc = _qq_group_set_my_status_desc(group);
+        group->internal_group_id = internal_id;
+        group->external_group_id = external_id;
+        group->group_type = 0x01;       /* assume permanent Qun */
+        group->creator_uid = 10000;     /* assume by QQ admin */
+        group->group_category = 0x01;
+        group->auth_type = 0x02;        /* assume need auth */
+        group->group_name_utf8 = g_strdup(group_name_utf8 == NULL ? "" : group_name_utf8);
+        group->group_desc_utf8 = g_strdup("");
+        group->notice_utf8 = g_strdup("");
+        group->members = NULL;
+
+        qd->groups = g_list_append(qd->groups, group);
+        _qq_group_add_to_blist(gc, group);
+
+        return group;
+}
+
+void qq_group_delete_internal_record(qq_data *qd, guint32 internal_group_id)
+{
+        qq_group *group;
+        GList *list;
+        g_return_if_fail(qd != NULL);
+
+        list = qd->groups;
+        while (list != NULL) {
+                group = (qq_group *) qd->groups->data;
+                if (internal_group_id == group->internal_group_id) {
+                        qd->groups = g_list_remove(qd->groups, group);
+                        qq_group_free(group);
+                        break;
+                } else {
+                        list = list->next;
+                }
+        }
+}
+
+/* convert a qq_group to hash-table, which could be component of GaimChat */
+GHashTable *qq_group_to_hashtable(qq_group *group)
+{
+	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_GROUP_KEY_MEMBER_STATUS), g_strdup_printf("%d", group->my_status));
+	group->my_status_desc = _qq_group_set_my_status_desc(group);
+
+	g_hash_table_insert(components,
+			    g_strdup(QQ_GROUP_KEY_INTERNAL_ID), g_strdup_printf("%d", group->internal_group_id));
+	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_EXTERNAL_ID),
+			    g_strdup_printf("%d", group->external_group_id));
+	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_GROUP_TYPE), g_strdup_printf("%d", group->group_type));
+	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_CREATOR_UID), g_strdup_printf("%d", group->creator_uid));
+	g_hash_table_insert(components,
+			    g_strdup(QQ_GROUP_KEY_GROUP_CATEGORY), g_strdup_printf("%d", group->group_category));
+	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_AUTH_TYPE), g_strdup_printf("%d", group->auth_type));
+	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_MEMBER_STATUS_DESC), g_strdup(group->my_status_desc));
+	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_GROUP_NAME_UTF8), g_strdup(group->group_name_utf8));
+	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_GROUP_DESC_UTF8), g_strdup(group->group_desc_utf8));
+	return components;
+}
+
+/* create a qq_group from hashtable */
+qq_group *qq_group_from_hashtable(GaimConnection *gc, GHashTable *data)
+{
+	qq_data *qd;
+	qq_group *group;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, NULL);
+	g_return_val_if_fail(data != NULL, NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	group = g_new0(qq_group, 1);
+	group->my_status =
+	    qq_string_to_dec_value
+	    (NULL ==
+	     g_hash_table_lookup(data,
+				 QQ_GROUP_KEY_MEMBER_STATUS) ?
+	     g_strdup_printf("%d",
+			     QQ_GROUP_MEMBER_STATUS_NOT_MEMBER) :
+	     g_hash_table_lookup(data, QQ_GROUP_KEY_MEMBER_STATUS));
+	group->internal_group_id = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_INTERNAL_ID));
+	group->external_group_id = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_EXTERNAL_ID));
+	group->group_type = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_GROUP_TYPE));
+	group->creator_uid = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_CREATOR_UID));
+	group->group_category = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_GROUP_CATEGORY));
+	group->auth_type = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_AUTH_TYPE));
+	group->group_name_utf8 = g_strdup(g_hash_table_lookup(data, QQ_GROUP_KEY_GROUP_NAME_UTF8));
+	group->group_desc_utf8 = g_strdup(g_hash_table_lookup(data, QQ_GROUP_KEY_GROUP_DESC_UTF8));
+	group->my_status_desc = _qq_group_set_my_status_desc(group);
+
+	qd->groups = g_list_append(qd->groups, group);
+
+	return group;
+}
+
+/* refresh group local subscription */
+void qq_group_refresh(GaimConnection *gc, qq_group *group)
+{
+	GaimChat *chat;
+	gchar *external_group_id;
+	g_return_if_fail(gc != NULL && group != NULL);
+
+	external_group_id = g_strdup_printf("%d", group->external_group_id);
+	chat = gaim_blist_find_chat(gaim_connection_get_account(gc), external_group_id);
+	g_free(external_group_id);
+	if (chat == NULL && group->my_status != QQ_GROUP_MEMBER_STATUS_NOT_MEMBER) {
+		_qq_group_add_to_blist(gc, group);
+	} else if (chat != NULL) {	/* we have a local record, update its info */
+		/* if there is group_name_utf8, we update the group name */
+		if (group->group_name_utf8 != NULL && strlen(group->group_name_utf8) > 0)
+			gaim_blist_alias_chat(chat, group->group_name_utf8);
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_MEMBER_STATUS), g_strdup_printf("%d", group->my_status));
+		group->my_status_desc = _qq_group_set_my_status_desc(group);
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_MEMBER_STATUS_DESC), g_strdup(group->my_status_desc));
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_INTERNAL_ID),
+				     g_strdup_printf("%d", group->internal_group_id));
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_EXTERNAL_ID),
+				     g_strdup_printf("%d", group->external_group_id));
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_GROUP_TYPE), g_strdup_printf("%d", group->group_type));
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_CREATOR_UID), g_strdup_printf("%d", group->creator_uid));
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_GROUP_CATEGORY),
+				     g_strdup_printf("%d", group->group_category));
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_AUTH_TYPE), g_strdup_printf("%d", group->auth_type));
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_GROUP_NAME_UTF8), g_strdup(group->group_name_utf8));
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_GROUP_DESC_UTF8), g_strdup(group->group_desc_utf8));
+	}
+}
+
+/* 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)
+{
+	if (pending) 
+		*list = g_slist_prepend(*list, GINT_TO_POINTER(id));
+	else 
+		*list = g_slist_remove(*list, GINT_TO_POINTER(id));
+}
+
+/* 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));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgaim/protocols/qq/group_internal.h	Fri Sep 01 11:03:18 2006 +0000
@@ -0,0 +1,52 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _QQ_GROUP_HASH_H_
+#define _QQ_GROUP_HASH_H_
+
+#include <glib.h>
+#include "group.h"
+
+#define QQ_GROUP_KEY_MEMBER_STATUS      "my_status_code"
+#define QQ_GROUP_KEY_MEMBER_STATUS_DESC "my_status_desc"
+#define QQ_GROUP_KEY_INTERNAL_ID        "internal_group_id"
+#define QQ_GROUP_KEY_EXTERNAL_ID        "external_group_id"
+#define QQ_GROUP_KEY_GROUP_TYPE         "group_type"
+#define QQ_GROUP_KEY_CREATOR_UID        "creator_uid"
+#define QQ_GROUP_KEY_GROUP_CATEGORY     "group_category"
+#define QQ_GROUP_KEY_AUTH_TYPE          "auth_type"
+#define QQ_GROUP_KEY_GROUP_NAME_UTF8    "group_name_utf8"
+#define QQ_GROUP_KEY_GROUP_DESC_UTF8    "group_desc_utf8"
+
+qq_group *qq_group_create_internal_record(GaimConnection *gc, 
+		guint32 internal_id, guint32 external_id, gchar *group_name_utf8);
+void qq_group_delete_internal_record(qq_data *qd, guint32 internal_group_id);
+
+GHashTable *qq_group_to_hashtable(qq_group *group);
+qq_group *qq_group_from_hashtable(GaimConnection *gc, GHashTable *data);
+
+void qq_group_refresh(GaimConnection *gc, qq_group *group);
+
+void qq_set_pending_id(GSList **list, guint32 id, gboolean pending);
+GSList *qq_get_pending_id(GSList *list, guint32 id);
+
+#endif
--- a/libgaim/protocols/qq/group_join.c	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/group_join.c	Fri Sep 01 11:03:18 2006 +0000
@@ -30,11 +30,12 @@
 #include "group_conv.h"
 #include "group_find.h"
 #include "group_free.h"
-#include "group_hash.h"
+#include "group_internal.h"
 #include "group_info.h"
 #include "group_join.h"
 #include "group_opt.h"
 #include "group_network.h"
+#include "group_search.h"
 
 enum {
 	QQ_GROUP_JOIN_OK = 0x01,
@@ -51,24 +52,37 @@
 	gc = g->gc;
 	internal_group_id = g->uid;
 
-	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 	g_return_if_fail(group != NULL);
 
 	qq_send_cmd_group_exit_group(gc, group);
 }
 
 /* send packet to join a group without auth */
-static void _qq_send_cmd_group_join_group(GaimConnection *gc, qq_group *group)
+void qq_send_cmd_group_join_group(GaimConnection *gc, qq_group *group)
 {
 	guint8 *raw_data, *cursor;
 	gint bytes, data_len;
 
 	g_return_if_fail(gc != NULL && group != NULL);
+
 	if (group->my_status == QQ_GROUP_MEMBER_STATUS_NOT_MEMBER) {
 		group->my_status = QQ_GROUP_MEMBER_STATUS_APPLYING;
 		qq_group_refresh(gc, group);
 	}
 
+	switch (group->auth_type) {
+	case QQ_GROUP_AUTH_TYPE_NO_AUTH:
+	case QQ_GROUP_AUTH_TYPE_NEED_AUTH:
+		break;
+	case QQ_GROUP_AUTH_TYPE_NO_ADD:
+		gaim_notify_warning(gc, NULL, _("This group does not allow others to join"), NULL);
+		return;
+	default:
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Unknown group auth type: %d\n", group->auth_type);
+		break;
+	}
+
 	data_len = 5;
 	raw_data = g_newa(guint8, data_len);
 	cursor = raw_data;
@@ -94,7 +108,7 @@
 	gc = g->gc;
 	internal_group_id = g->uid;
 
-	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 	if (group == NULL) {
 		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Can not find qq_group by internal_id: %d\n", internal_group_id);
 		return;
@@ -163,15 +177,7 @@
 		qq_send_group_cmd(gc, group, raw_data, data_len);
 }
 
-/* send packet to exit one group
- * In fact, this will never be used for GAIM
- * when we remove a GaimChat node, there is no user controlable callback
- * so we only remove the GaimChat node,
- * but we never use this cmd to update the server side
- * anyway, it is function, as when we remove the GaimChat node,
- * user has no way to start up the chat conversation window
- * therefore even we are still in it, 
- * the group IM will not show up to bother us. (Limited by GAIM) */
+/* send a packet to exit a group */
 void qq_send_cmd_group_exit_group(GaimConnection *gc, qq_group *group)
 {
 	guint8 *raw_data, *cursor;
@@ -212,16 +218,16 @@
 	bytes += read_packet_dw(data, cursor, len, &internal_group_id);
 
 	if (bytes == expected_bytes) {
-		group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+		group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 		if (group != NULL) {
 			chat =
 			    gaim_blist_find_chat
 			    (gaim_connection_get_account(gc), g_strdup_printf("%d", group->external_group_id));
 			if (chat != NULL)
 				gaim_blist_remove_chat(chat);
-			qq_group_remove_by_internal_group_id(qd, internal_group_id);
+			qq_group_delete_internal_record(qd, internal_group_id);
 		}
-		gaim_notify_info(gc, _("QQ Qun Operation"), _("You have successfully exit group"), NULL);
+		gaim_notify_info(gc, _("QQ Qun Operation"), _("You have successfully exited the group"), NULL);
 	} else {
 		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
 			   "Invalid exit group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes);
@@ -246,7 +252,8 @@
 
 	if (bytes == expected_bytes)
 		gaim_notify_info
-		    (gc, _("QQ Group Auth"), _("You authorization operation has been accepted by QQ server"), NULL);
+		    (gc, _("QQ Group Auth"), 
+		     _("Your authorization operation has been accepted by the QQ server"), NULL);
 	else
 		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
 			   "Invalid join group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes);
@@ -272,7 +279,7 @@
 			   "Invalid join group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes);
 		return;
 	} else {		/* join group OK */
-		group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+		group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 		/* need to check if group is NULL or not. */
 		g_return_if_fail(group != NULL);
 		switch (reply) {
@@ -280,7 +287,7 @@
 			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Succeed joining group \"%s\"\n", group->group_name_utf8);
 			group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER;
 			qq_group_refresh(gc, group);
-			/* this must be show before getting online member */
+			/* this must be shown before getting online members */
 			qq_group_conv_show_window(gc, group);
 			qq_send_cmd_group_get_group_info(gc, group);
 			break;
@@ -300,38 +307,33 @@
 	}
 }
 
-/* Apply to join one group without auth */
+/* Attempt to join a group without auth */
 void qq_group_join(GaimConnection *gc, GHashTable *data)
 {
-	gchar *internal_group_id_ptr;
-	guint32 internal_group_id;
+	qq_data *qd;
+	gchar *external_group_id_ptr;
+	guint32 external_group_id;
 	qq_group *group;
 
-	g_return_if_fail(gc != NULL && data != NULL);
-
-	internal_group_id_ptr = g_hash_table_lookup(data, "internal_group_id");
-	internal_group_id = strtol(internal_group_id_ptr, NULL, 10);
-
-	g_return_if_fail(internal_group_id > 0);
-
-	/* for those we have subscribed, they should have been put into
-	 * qd->groups in qq_group_init subroutine */
-	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
-	if (group == NULL)
-		group = qq_group_from_hashtable(gc, data);
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL && data != NULL);
+	qd = (qq_data *) gc->proto_data;
 
-	g_return_if_fail(group != NULL);
+	external_group_id_ptr = g_hash_table_lookup(data, QQ_GROUP_KEY_EXTERNAL_ID);
+	g_return_if_fail(external_group_id_ptr != NULL);
+	errno = 0;
+	external_group_id = strtol(external_group_id_ptr, NULL, 10);
+	if (errno != 0) {
+		gaim_notify_error(gc, _("Error"), 
+				_("You inputted a group id outside the acceptable range"), NULL);
+		return;
+	}
 
-	switch (group->auth_type) {
-	case QQ_GROUP_AUTH_TYPE_NO_AUTH:
-	case QQ_GROUP_AUTH_TYPE_NEED_AUTH:
-		_qq_send_cmd_group_join_group(gc, group);
-		break;
-	case QQ_GROUP_AUTH_TYPE_NO_ADD:
-		gaim_notify_warning(gc, NULL, _("This group does not allow others to join"), NULL);
-		break;
-	default:
-		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Unknown group auth type: %d\n", group->auth_type);
+	group = qq_group_find_by_id(gc, external_group_id, QQ_EXTERNAL_ID);
+	if (group) {
+		qq_send_cmd_group_join_group(gc, group);
+	} else {
+		qq_set_pending_id(&qd->joining_groups, external_group_id, TRUE);
+		qq_send_cmd_group_search_group(gc, external_group_id);
 	}
 }
 
--- a/libgaim/protocols/qq/group_join.h	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/group_join.h	Fri Sep 01 11:03:18 2006 +0000
@@ -41,6 +41,7 @@
 
 void qq_send_cmd_group_auth(GaimConnection *gc, qq_group *group, guint8 opt, guint32 uid, const gchar *reason_utf8);
 void qq_group_join(GaimConnection *gc, GHashTable *data);
+void qq_send_cmd_group_join_group(GaimConnection *gc, qq_group *group);
 void qq_group_exit(GaimConnection *gc, GHashTable *data);
 void qq_send_cmd_group_exit_group(GaimConnection *gc, qq_group *group);
 void qq_process_group_cmd_exit_group(guint8 *data, guint8 **cursor, gint len, GaimConnection *gc);
--- a/libgaim/protocols/qq/group_network.c	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/group_network.c	Fri Sep 01 11:03:18 2006 +0000
@@ -27,7 +27,7 @@
 #include "crypt.h"
 #include "group_conv.h"
 #include "group_find.h"
-#include "group_hash.h"
+#include "group_internal.h"
 #include "group_im.h"
 #include "group_info.h"
 #include "group_join.h"
@@ -40,6 +40,7 @@
 
 enum {
 	QQ_GROUP_CMD_REPLY_OK = 0x00,
+	QQ_GROUP_CMD_REPLY_SEARCH_ERROR = 0x02,
 	QQ_GROUP_CMD_REPLY_NOT_MEMBER = 0x0a
 };
 
@@ -154,11 +155,15 @@
 		bytes += read_packet_b(data, &cursor, len, &sub_cmd);
 		bytes += read_packet_b(data, &cursor, len, &reply);
 
-		group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+		group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 
 		if (reply != QQ_GROUP_CMD_REPLY_OK) {
 			gaim_debug(GAIM_DEBUG_WARNING, "QQ",
 				   "Group cmd reply says cmd %s fails\n", qq_group_cmd_get_desc(sub_cmd));
+
+			if (group != NULL)
+				qq_set_pending_id(&qd->joining_groups, group->external_group_id, FALSE);
+
 			switch (reply) {	/* this should be all errors */
 			case QQ_GROUP_CMD_REPLY_NOT_MEMBER:
 				if (group != NULL) {
@@ -169,19 +174,26 @@
 					qq_group_refresh(gc, group);
 				}
 				break;
+			case QQ_GROUP_CMD_REPLY_SEARCH_ERROR:
+				if (qd->roomlist != NULL) {
+					if (gaim_roomlist_get_in_progress(qd->roomlist))
+						gaim_roomlist_set_in_progress(qd->roomlist, FALSE);
+				}
+				_qq_process_group_cmd_reply_error_default(reply, cursor, len - bytes, gc);
+				break;
 			default:
 				_qq_process_group_cmd_reply_error_default(reply, cursor, len - bytes, gc);
 			}
 			return;
 		}
 
-		/* seems to ok so far, so we process the reply according to sub_cmd */
+		/* seems ok so far, so we process the reply according to sub_cmd */
 		switch (sub_cmd) {
 		case QQ_GROUP_CMD_GET_GROUP_INFO:
 			qq_process_group_cmd_get_group_info(data, &cursor, len, gc);
 			if (group != NULL) {
-				qq_send_cmd_group_get_member_info(gc, group);
-				qq_send_cmd_group_get_online_member(gc, group);
+				qq_send_cmd_group_get_members_info(gc, group);
+				qq_send_cmd_group_get_online_members(gc, group);
 			}
 			break;
 		case QQ_GROUP_CMD_CREATE_GROUP:
@@ -212,12 +224,12 @@
 			qq_process_group_cmd_im(data, &cursor, len, gc);
 			break;
 		case QQ_GROUP_CMD_GET_ONLINE_MEMBER:
-			qq_process_group_cmd_get_online_member(data, &cursor, len, gc);
+			qq_process_group_cmd_get_online_members(data, &cursor, len, gc);
 			if (group != NULL)
 				qq_group_conv_refresh_online_member(gc, group);
 			break;
 		case QQ_GROUP_CMD_GET_MEMBER_INFO:
-			qq_process_group_cmd_get_member_info(data, &cursor, len, gc);
+			qq_process_group_cmd_get_members_info(data, &cursor, len, gc);
 			if (group != NULL)
 				qq_group_conv_refresh_online_member(gc, group);
 			break;
--- a/libgaim/protocols/qq/group_opt.c	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/group_opt.c	Fri Sep 01 11:03:18 2006 +0000
@@ -26,9 +26,8 @@
 
 #include "buddy_info.h"
 #include "char_conv.h"
-/*#include "group_admindlg.h"	*/
 #include "group_find.h"
-#include "group_hash.h"
+#include "group_internal.h"
 #include "group_info.h"
 #include "group_join.h"
 #include "group_network.h"
@@ -108,7 +107,7 @@
 {
 	qq_group *group;
 	g_return_if_fail(g != NULL && g->gc != NULL && g->internal_group_id > 0 && g->member > 0);
-	group = qq_group_find_by_internal_group_id(g->gc, g->internal_group_id);
+	group = qq_group_find_by_id(g->gc, g->internal_group_id, QQ_INTERNAL_ID);
 	g_return_if_fail(group != NULL);
 	qq_send_cmd_group_auth(g->gc, group, QQ_GROUP_AUTH_REQUEST_REJECT, g->member, msg_utf8);
 	g_free(g);
@@ -148,7 +147,7 @@
 {
 	qq_group *group;
 	g_return_if_fail(g != NULL && g->gc != NULL && g->internal_group_id > 0 && g->member > 0);
-	group = qq_group_find_by_internal_group_id(g->gc, g->internal_group_id);
+	group = qq_group_find_by_id(g->gc, g->internal_group_id, QQ_INTERNAL_ID);
 	g_return_if_fail(group != NULL);
 	qq_send_cmd_group_auth(g->gc, group, QQ_GROUP_AUTH_REQUEST_APPROVE, g->member, "");
 	qq_group_find_or_add_member(g->gc, group, g->member);
@@ -221,7 +220,7 @@
 	g_return_if_fail(internal_group_id > 0);
 
 	/* we should have its info locally */
-	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 	g_return_if_fail(group != NULL);
 
 	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Succeed in modify members for Qun %d\n", group->external_group_id);
@@ -290,7 +289,7 @@
 	g_return_if_fail(internal_group_id > 0);
 
 	/* we should have its info locally */
-	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 	g_return_if_fail(group != NULL);
 
 	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Succeed in modify info for Qun %d\n", group->external_group_id);
@@ -345,10 +344,10 @@
 	qq_group *group;
 	g_return_if_fail(g != NULL && g->gc != NULL && g->uid > 0);
 
-	group = qq_group_find_by_internal_group_id(g->gc, g->uid);
+	group = qq_group_find_by_id(g->gc, g->uid, QQ_INTERNAL_ID);
 	g_return_if_fail(group != NULL);
 
-	/* XXX insert UI code here */
+	/* TODO insert UI code here */
 	/* qq_group_detail_window_show(g->gc, group); */
 	g_free(g);
 }
@@ -368,7 +367,7 @@
 	read_packet_dw(data, cursor, len, &external_group_id);
 	g_return_if_fail(internal_group_id > 0 && external_group_id);
 
-	group = qq_group_create_by_id(gc, internal_group_id, external_group_id);
+	group = qq_group_create_internal_record(gc, internal_group_id, external_group_id, NULL);
 	group->my_status = QQ_GROUP_MEMBER_STATUS_IS_ADMIN;
 	group->creator_uid = qd->uid;
 	qq_group_refresh(gc, group);
@@ -427,7 +426,7 @@
 	g_return_if_fail(internal_group_id > 0);
 
 	/* we should have its info locally */
-	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 	g_return_if_fail(group != NULL);
 
 	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Succeed in activate Qun %d\n", group->external_group_id);
@@ -445,7 +444,7 @@
 	internal_group_id = strtol(internal_group_id_ptr, NULL, 10);
 	g_return_if_fail(internal_group_id > 0);
 
-	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 	g_return_if_fail(group != NULL);
 
 	/* XXX insert UI code here */
--- a/libgaim/protocols/qq/group_search.c	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/group_search.c	Fri Sep 01 11:03:18 2006 +0000
@@ -23,7 +23,10 @@
 #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_network.h"
 #include "group_search.h"
 #include "utils.h"
@@ -58,6 +61,37 @@
 		qq_send_group_cmd(gc, NULL, raw_data, data_len);
 }
 
+static void _qq_setup_roomlist(qq_data *qd, qq_group *group)
+{
+	GaimRoomlistRoom *room;
+	gchar *field;
+
+	room = gaim_roomlist_room_new(GAIM_ROOMLIST_ROOMTYPE_ROOM, group->group_name_utf8, NULL);
+	field = g_strdup_printf("%d", group->external_group_id);
+	gaim_roomlist_room_add_field(qd->roomlist, room, field);
+	g_free(field);
+	field = g_strdup_printf("%d", group->creator_uid);
+	gaim_roomlist_room_add_field(qd->roomlist, room, field);
+	g_free(field);
+	gaim_roomlist_room_add_field(qd->roomlist, room, group->group_desc_utf8);
+	field = g_strdup_printf("%d", group->internal_group_id);
+	gaim_roomlist_room_add_field(qd->roomlist, room, field);
+	g_free(field);
+	field = g_strdup_printf("%d", group->group_type);
+	gaim_roomlist_room_add_field(qd->roomlist, room, field);
+	g_free(field);
+	field = g_strdup_printf("%d", group->auth_type);
+	gaim_roomlist_room_add_field(qd->roomlist, room, field);
+	g_free(field);
+	field = g_strdup_printf("%d", group->group_category);
+	gaim_roomlist_room_add_field(qd->roomlist, room, field);
+	g_free(field);
+	gaim_roomlist_room_add_field(qd->roomlist, room, group->group_name_utf8);
+	gaim_roomlist_room_add(qd->roomlist, room);
+
+	gaim_roomlist_set_in_progress(qd->roomlist, FALSE);
+}
+
 /* process group cmd reply "search group" */
 void qq_process_group_cmd_search_group(guint8 *data, guint8 **cursor, gint len, GaimConnection *gc)
 {
@@ -65,56 +99,51 @@
 	guint16 unknown;
 	gint bytes, pascal_len, i;
 	qq_data *qd;
-	GaimRoomlistRoom *room;
 	qq_group *group;
+	GSList *pending_id;
 
 	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
 	g_return_if_fail(data != NULL && len > 0);
 	qd = (qq_data *) gc->proto_data;
 
-	i = 0;
 	read_packet_b(data, cursor, len, &search_type);
 	group = g_newa(qq_group, 1);
 
 	/* now it starts with group_info_entry */
-	while (*cursor < (data + len)) {	/* still have data to read */
-		/* begin of one qq_group */
-		bytes = 0;
-		i++;
-		bytes += read_packet_dw(data, cursor, len, &(group->internal_group_id));
-		bytes += read_packet_dw(data, cursor, len, &(group->external_group_id));
-		bytes += read_packet_b(data, cursor, len, &(group->group_type));
-		bytes += read_packet_w(data, cursor, len, &(unknown));
-		bytes += read_packet_w(data, cursor, len, &(unknown));
-		bytes += read_packet_dw(data, cursor, len, &(group->creator_uid));
-		bytes += read_packet_w(data, cursor, len, &(unknown));
-		bytes += read_packet_w(data, cursor, len, &(unknown));
-		bytes += read_packet_w(data, cursor, len, &(unknown));
-		bytes += read_packet_dw(data, cursor, len, &(group->group_category));
-		pascal_len = convert_as_pascal_string(*cursor, &(group->group_name_utf8), QQ_CHARSET_DEFAULT);
-		bytes += pascal_len;
-		*cursor += pascal_len;
-		bytes += read_packet_w(data, cursor, len, &(unknown));
-		bytes += read_packet_b(data, cursor, len, &(group->auth_type));
-		pascal_len = convert_as_pascal_string(*cursor, &(group->group_desc_utf8), QQ_CHARSET_DEFAULT);
-		bytes += pascal_len;
-		*cursor += pascal_len;
-		/* end of one qq_group */
-		room = gaim_roomlist_room_new(GAIM_ROOMLIST_ROOMTYPE_ROOM, group->group_name_utf8, NULL);
-		gaim_roomlist_room_add_field(qd->roomlist, room, g_strdup_printf("%d", group->external_group_id));
-		gaim_roomlist_room_add_field(qd->roomlist, room, g_strdup_printf("%d", group->creator_uid));
-		gaim_roomlist_room_add_field(qd->roomlist, room, group->group_desc_utf8);
-		gaim_roomlist_room_add_field(qd->roomlist, room, g_strdup_printf("%d", group->internal_group_id));
-		gaim_roomlist_room_add_field(qd->roomlist, room, g_strdup_printf("%d", group->group_type));
-		gaim_roomlist_room_add_field(qd->roomlist, room, g_strdup_printf("%d", group->auth_type));
-		gaim_roomlist_room_add_field(qd->roomlist, room, g_strdup_printf("%d", group->group_category));
-		gaim_roomlist_room_add_field(qd->roomlist, room, group->group_name_utf8);
-		gaim_roomlist_room_add(qd->roomlist, room);
-	}
-        if(*cursor > (data + len)) {
+	bytes = 0;
+	i++;
+	bytes += read_packet_dw(data, cursor, len, &(group->internal_group_id));
+	bytes += read_packet_dw(data, cursor, len, &(group->external_group_id));
+	bytes += read_packet_b(data, cursor, len, &(group->group_type));
+	bytes += read_packet_w(data, cursor, len, &(unknown));
+	bytes += read_packet_w(data, cursor, len, &(unknown));
+	bytes += read_packet_dw(data, cursor, len, &(group->creator_uid));
+	bytes += read_packet_w(data, cursor, len, &(unknown));
+	bytes += read_packet_w(data, cursor, len, &(unknown));
+	bytes += read_packet_w(data, cursor, len, &(unknown));
+	bytes += read_packet_dw(data, cursor, len, &(group->group_category));
+	pascal_len = convert_as_pascal_string(*cursor, &(group->group_name_utf8), QQ_CHARSET_DEFAULT);
+	bytes += pascal_len;
+	*cursor += pascal_len;
+	bytes += read_packet_w(data, cursor, len, &(unknown));
+	bytes += read_packet_b(data, cursor, len, &(group->auth_type));
+	pascal_len = convert_as_pascal_string(*cursor, &(group->group_desc_utf8), QQ_CHARSET_DEFAULT);
+	bytes += pascal_len;
+	*cursor += pascal_len;
+	/* end of one qq_group */
+        if(*cursor != (data + len)) {
                          gaim_debug(GAIM_DEBUG_ERROR, "QQ", 
 					 "group_cmd_search_group: Dangerous error! maybe protocol changed, notify developers!");
         }
-	gaim_roomlist_set_in_progress(qd->roomlist, FALSE);
-	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Search group reply: %d groups\n", i);
+
+	pending_id = qq_get_pending_id(qd->joining_groups, group->external_group_id);
+	if (pending_id != NULL) {
+		qq_set_pending_id(&qd->joining_groups, group->external_group_id, FALSE);
+		if (qq_group_find_by_id(gc, group->internal_group_id, QQ_INTERNAL_ID) == NULL)
+			qq_group_create_internal_record(gc, 
+					group->internal_group_id, group->external_group_id, group->group_name_utf8);
+		qq_send_cmd_group_join_group(gc, group);
+	} else {
+		_qq_setup_roomlist(qd, group);
+	}
 }
--- a/libgaim/protocols/qq/login_logout.c	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/login_logout.c	Fri Sep 01 11:03:18 2006 +0000
@@ -234,10 +234,10 @@
 
 	qq_send_packet_change_status(gc);
 
-	/* now refresh buddy list */
-	/* changed by gfhuang, using With Qun version, error, not working still */
+	/* refresh buddies */
 	qq_send_packet_get_buddies_list(gc, QQ_FRIENDS_LIST_POSITION_START);
-	/* qq_send_packet_get_all_list_with_group(gc, QQ_FRIENDS_LIST_POSITION_START); */
+	/* refresh groups */
+	qq_send_packet_get_all_list_with_group(gc, QQ_FRIENDS_LIST_POSITION_START);
 
 	return QQ_LOGIN_REPLY_OK;
 }
--- a/libgaim/protocols/qq/qq.c	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/qq.c	Fri Sep 01 11:03:18 2006 +0000
@@ -153,7 +153,7 @@
 	/* do not use g_return_val_if_fail, as it is not assertion */
 	if (b == NULL || b->proto_data == NULL)
 		return "qq";
-
+	
 	q_bud = (qq_buddy *) b->proto_data;
 	filename = get_icon_name(q_bud->icon / 3 + 1);
 
@@ -565,7 +565,6 @@
 }
 */
 
-/* XXX re-enable this
 static void _qq_menu_unsubscribe_group(GaimBlistNode * node)
 {
 	GaimChat *chat = (GaimChat *)node;
@@ -578,7 +577,7 @@
 	qq_group_exit(gc, components);
 }
 
-// XXX re-enable this
+/*
 static void _qq_menu_manage_group(GaimBlistNode * node)
 {
 	GaimChat *chat = (GaimChat *)node;
@@ -860,7 +859,6 @@
 }
 
 /* chat-related (QQ Qun) menu shown up with right-click */
-/* TODO re-enable this
 static GList *_qq_chat_menu(GaimBlistNode *node)
 {
 	GList *m;
@@ -870,14 +868,15 @@
 	act = gaim_menu_action_new(_("Exit this QQ Qun"), GAIM_CALLBACK(_qq_menu_unsubscribe_group), NULL, NULL);
 	m = g_list_append(m, act);
 
+	/* TODO: enable this
 	act = gaim_menu_action_new(_("Show Details"), GAIM_CALLBACK(_qq_menu_manage_group), NULL, NULL);
 	m = g_list_append(m, act);
+	*/
 
 	return m;
 }
-*/
+
 /* buddy-related menu shown up with right-click */
-/* TODO re-enable this
 static GList *_qq_buddy_menu(GaimBlistNode * node)
 {
 	GList *m;
@@ -886,7 +885,9 @@
 		return _qq_chat_menu(node);
 	
 	m = NULL;
-*/
+	return m;
+}
+
 /* TODO : not working, temp commented out by gfhuang 
 
 	act = gaim_menu_action_new(_("Block this buddy"), GAIM_CALLBACK(_qq_menu_block_buddy), NULL, NULL); //add NULL by gfhuang
@@ -918,7 +919,7 @@
 		if (group->my_status == QQ_GROUP_MEMBER_STATUS_IS_MEMBER ||
 		    group->my_status == QQ_GROUP_MEMBER_STATUS_IS_ADMIN)
 			/* no need to get info time and time again, online members enough */
-			qq_send_cmd_group_get_online_member(gc, group);
+			qq_send_cmd_group_get_online_members(gc, group);
 	
 		list = list->next;
 	}
@@ -934,7 +935,7 @@
 	gchar *gaim_name;
 	g_return_if_fail(gc != NULL && gc->proto_data != NULL && who != NULL);
 
-	gaim_name = qq_group_find_member_by_channel_and_nickname(gc, channel, who);
+	gaim_name = chat_name_to_gaim_name(who);
 	if (gaim_name != NULL)
 		_qq_get_info(gc, gaim_name);
 }
@@ -944,7 +945,7 @@
 static gchar *_qq_get_chat_buddy_real_name(GaimConnection *gc, gint channel, const gchar *who)
 {
 	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL && who != NULL, NULL);
-	return qq_group_find_member_by_channel_and_nickname(gc, channel, who);
+	return chat_name_to_gaim_name(who);
 }
 
 void qq_function_not_implemented(GaimConnection *gc)
@@ -964,9 +965,9 @@
 	_qq_status_text,		/* status_text	*/
 	_qq_tooltip_text,		/* tooltip_text */
 	_qq_away_states,		/* away_states	*/
-	NULL,				/* blist_node_menu */
-	NULL,				/* chat_info */
-	NULL,				/* chat_info_defaults */
+	_qq_buddy_menu,			/* blist_node_menu */
+	qq_chat_info,			/* chat_info */
+	qq_chat_info_defaults,		/* chat_info_defaults */
 	_qq_login,			/* login */
 	_qq_close,			/* close */
 	_qq_send_im,			/* send_im */
@@ -1043,7 +1044,7 @@
 
 	NULL,				/**< ui_info		*/
 	&prpl_info,			/**< extra_info		*/
-	NULL,			/**< prefs_info		*/
+	NULL,				/**< prefs_info		*/
 	_qq_actions
 };
 
--- a/libgaim/protocols/qq/qq.h	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/qq.h	Fri Sep 01 11:03:18 2006 +0000
@@ -95,9 +95,11 @@
 
 	GList *groups;
 	GList *group_packets;
+	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 *qun_info_window;
+	GList *group_info_window;
 	GList *sendqueue;
 	GList *info_query;
 	GList *add_buddy_request;
--- a/libgaim/protocols/qq/utils.c	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/utils.c	Fri Sep 01 11:03:18 2006 +0000
@@ -164,13 +164,6 @@
 	return g_strdup_printf("qq_%d", set);
 }
 
-/* convert a QQ UID to a unique name of Gaim
- * the return needs to be freed */
-gchar *uid_to_gaim_name(guint32 uid)
-{
-	return g_strdup_printf(QQ_NAME_FORMAT, uid);
-}
-
 /* convert Gaim name to original QQ UID */
 guint32 gaim_name_to_uid(const gchar *const name)
 {
@@ -184,6 +177,27 @@
 		return ret;
 }
 
+/* convert a QQ UID to a unique name of Gaim
+ * the return needs to be freed */
+gchar *uid_to_gaim_name(guint32 uid)
+{
+	return g_strdup_printf(QQ_NAME_FORMAT, uid);
+}
+
+/* convert name displayed in a chat channel to original QQ UID */
+gchar *chat_name_to_gaim_name(const gchar *const name)
+{
+	const gchar *tmp; 
+	gchar *ret;
+
+	g_return_val_if_fail(name != NULL, NULL);
+
+	tmp = (gchar *) gaim_strcasestr(name, "(qq-");
+	ret = g_strndup(tmp + 4, strlen(name) - (tmp - name) - 4 - 1);
+
+	return ret;
+}
+
 /* try to dump the data as GBK */
 void try_dump_as_gbk(const guint8 *const data, gint len)
 {
--- a/libgaim/protocols/qq/utils.h	Fri Sep 01 10:05:30 2006 +0000
+++ b/libgaim/protocols/qq/utils.h	Fri Sep 01 11:03:18 2006 +0000
@@ -32,11 +32,13 @@
 
 gchar **split_data(guint8 *data, gint len, const gchar *delimit, gint expected_fields);
 guint8 *_gen_session_md5(gint uid, guint8 *session_key);
+
 gchar *gen_ip_str(guint8 *ip);
 guint8 *str_ip_gen(gchar *str);
-gchar *uid_to_gaim_name(guint32 uid);
 
 guint32 gaim_name_to_uid(const gchar *name);
+gchar *uid_to_gaim_name(guint32 uid);
+gchar *chat_name_to_gaim_name(const gchar *const name);
 
 gchar *get_icon_name(gint set);