diff src/protocols/qq/buddy_info.c @ 13989:16102b9c5c4a

[gaim-migrate @ 16562] *Eliminated all Gtk-related code from the prpl. Notably, this included the group ("Qun") administrative dialog and a dialog for setting and viewing personal information. Code for the latter now uses the gaim UI, while the former is currently disabled. *Disabled a few non-functional/non-essential menu actions. These included: IP lookup, system logging, about dialog, and qq_buddy_menu. committer: Tailor Script <tailor@pidgin.im>
author Mark Huetsch <markhuetsch>
date Mon, 24 Jul 2006 13:39:12 +0000
parents 983fd420e86b
children e6977f9435a1
line wrap: on
line diff
--- a/src/protocols/qq/buddy_info.c	Mon Jul 24 09:25:48 2006 +0000
+++ b/src/protocols/qq/buddy_info.c	Mon Jul 24 13:39:12 2006 +0000
@@ -20,11 +20,10 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-// START OF FILE
-/*****************************************************************************/
 #include "internal.h"		// strlen, _("get_text)
 #include "debug.h"		// gaim_debug
 #include "notify.h"		// gaim_notify
+#include "request.h"		// gaim_request_fields_new
 
 #include "utils.h"		// uid_to_gaim_name
 #include "packet_parse.h"	// MAX_PACKET_SIZE
@@ -32,68 +31,295 @@
 #include "char_conv.h"		// qq_to_utf8
 #include "crypt.h"		// qq_crypt
 #include "header_info.h"	// cmd alias
-#include "infodlg.h"		// info_window
 #include "keep_alive.h"		// qq_update_buddy_contact
 #include "send_core.h"		// qq_send_cmd
 
-// amount of fiedls in user info
-#define QQ_CONTACT_FIELDS 				37
+// Below is all of the information necessary to reconstruct the various
+// information fields that one can set in the official client. When we need
+// to know about a specific field (e.g., should "city" be a choice
+// or text field?), we can simply look it up from the template. Note that
+// there are a number of unidentified fields.
+
+typedef struct _info_field {
+	gchar *title;
+	gchar *id;		// used by gaim_request fields
+	gint pos;
+	gchar *group;
+	gint group_pos;		// for display order in the UI
+	gint choice;		// indicates which character array contains the choices 
+	gboolean customizable;	// whether a user can enter any text as a value, regardless of choice arrays
+	gchar *value;
+} info_field;
+
+static const info_field info_template_data[] = {
+	{ N_("User ID"), "uid", 		0, QQ_MAIN_INFO, 0, QQ_NO_CHOICE, TRUE, NULL },	
+	{ N_("Nickname"), "nick",		1, QQ_MAIN_INFO, 1, QQ_NO_CHOICE, TRUE, NULL },	
+	{ N_("Country/Region"), "country",	2, QQ_MAIN_INFO, 5, QQ_COUNTRY, TRUE, NULL },
+	{ N_("Province/State"), "province",	3, QQ_MAIN_INFO, 6, QQ_PROVINCE, TRUE, NULL },
+	{ N_("Zipcode"), "zipcode",		4, QQ_EXTRA_INFO, 7, QQ_NO_CHOICE, TRUE, NULL },
+	{ N_("Address"), "address",		5, QQ_EXTRA_INFO, 6, QQ_NO_CHOICE, TRUE, NULL },
+	{ N_("Phone Number"), "tel",		6, QQ_EXTRA_INFO, 9, QQ_NO_CHOICE, TRUE, NULL },
+	{ N_("Age"), "age",			7, QQ_MAIN_INFO, 3, QQ_NO_CHOICE, TRUE, NULL },
+	{ N_("Gender"), "gender",		8, QQ_MAIN_INFO, 4, QQ_GENDER, FALSE, NULL },
+	{ N_("Name"), "name",			9, QQ_MAIN_INFO, 2, QQ_NO_CHOICE, TRUE, NULL },
+	{ N_("Email"), "email",			10, QQ_EXTRA_INFO, 5, QQ_NO_CHOICE, TRUE, NULL },
+	{ "pager_sn", "pager_sn",		11, QQ_MISC, 0, QQ_NO_CHOICE, TRUE, NULL },
+	{ "pager_num", "pager_num",		12, QQ_MISC, 1, QQ_NO_CHOICE, TRUE, NULL },
+	{ "pager_sp", "pager_sp",		13, QQ_MISC, 2, QQ_NO_CHOICE, TRUE, NULL },
+	{ "pager_base_num", "pager_base_num",	14, QQ_MISC, 3, QQ_NO_CHOICE, TRUE, NULL },
+	{ "pager_type", "pager_type",		15, QQ_MISC, 4, QQ_NO_CHOICE, TRUE, NULL },
+	{ N_("Occupation"), "occupation",	16, QQ_EXTRA_INFO, 1, QQ_OCCUPATION, TRUE, NULL },
+	{ N_("Homepage"), "homepage",		17, QQ_EXTRA_INFO, 10, QQ_NO_CHOICE, TRUE, NULL },
+	{ "auth_type", "auth_type",		18, QQ_MISC, 5, QQ_NO_CHOICE, TRUE, NULL },
+	{ "unknown1", "unknown1",		19, QQ_MISC, 6, QQ_NO_CHOICE, TRUE, NULL },
+	{ "unknown2", "unknown2",		20, QQ_MISC, 7, QQ_NO_CHOICE, TRUE, NULL },
+	{ "face", "face",			21, QQ_MISC, 8, QQ_NO_CHOICE, TRUE, NULL },
+	{ N_("Cellphone Number"), "hp_num",	22, QQ_EXTRA_INFO, 8, QQ_NO_CHOICE, TRUE, NULL },
+	{ "hp_type", "hp_type",			23, QQ_MISC, 9, QQ_NO_CHOICE, TRUE, NULL },
+	{ N_("Personal Introduction"), "intro",	24, QQ_PERSONAL_INTRO, 0, QQ_NO_CHOICE, TRUE, NULL },
+	{ N_("City"), "city",			25, QQ_MAIN_INFO, 7, QQ_NO_CHOICE, TRUE, NULL },
+	{ "unknown3", "unknown3",		26, QQ_MISC, 10, QQ_NO_CHOICE, TRUE, NULL },
+	{ "unknown4", "unknown4",		27, QQ_MISC, 11, QQ_NO_CHOICE, TRUE, NULL },
+	{ "unknown5", "unknown5",		28, QQ_MISC, 12, QQ_NO_CHOICE, TRUE, NULL },
+	{ "is_open_hp",	"is_open_hp",		29, QQ_MISC, 13, QQ_NO_CHOICE, TRUE, NULL },
+	{ "is_open_contact", "is_open_contact",	30, QQ_MISC, 14, QQ_NO_CHOICE, TRUE, NULL },
+	{ N_("College"), "college",		31, QQ_EXTRA_INFO, 4, QQ_NO_CHOICE, TRUE, NULL },
+	{ N_("Horoscope Symbol"), "horoscope",	32, QQ_EXTRA_INFO, 0, QQ_HOROSCOPE, FALSE, NULL },
+	{ N_("Zodiac Symbol"), "zodiac",	33, QQ_EXTRA_INFO, 2, QQ_ZODIAC, FALSE, NULL },
+	{ N_("Blood Type"), "blood",		34, QQ_EXTRA_INFO, 3, QQ_BLOOD, FALSE, NULL },
+	{ "qq_show", "qq_show",			35, QQ_MISC, 15, QQ_NO_CHOICE, TRUE, NULL },
+	{ "unknown6", "unknown6",		36, QQ_MISC, 16, QQ_NO_CHOICE, TRUE, NULL },
+	{ NULL,	NULL, 0, NULL, 0, 0, 0, NULL	}	//NULL termination
+};
+
+//TODO: translate these arrays to their English equivalents
+// and move these characters to the zh_CN po file
+static const gchar *horoscope_names[] = {
+	"-", "水瓶座", "双鱼座", "牡羊座", "金牛座",
+	"双子座", "巨蟹座", "狮子座", "处女座", "天秤座",
+        "天蝎座", "射手座", "魔羯座", NULL
+};
+
+static const gchar *zodiac_names[] = {
+	"-", "鼠", "牛", "虎", "兔",
+	"龙", "蛇", "马", "羊", "猴",
+	"鸡", "狗", "猪", NULL
+};
+
+static const gchar *blood_types[] = {
+	"其它", "A型", "B型", "O型", "AB型", NULL
+};
+
+static const gchar *genders[] = {
+	N_("Male"),
+	N_("Female"),
+	NULL
+};
+
+static const gchar *country_names[] = {
+        "中国", "中国香港", "中国澳门", "中国台湾",
+        "新加坡", "马来西亚", "美国", NULL
+};
+
+static const gchar *province_names[] = {
+        "北京", "天津", "上海", "重庆", "香港",
+        "河北", "山西", "内蒙古", "辽宁", "吉林",
+        "黑龙江", "江西", "浙江", "江苏", "安徽",
+        "福建", "山东", "河南", "湖北", "湖南",
+        "广东", "广西", "海南", "四川", "贵州",
+        "云南", "西藏", "陕西", "甘肃", "宁夏",
+        "青海", "新疆", "台湾", "澳门", NULL
+};
+
+static const gchar *occupation_names[] = {
+        "全职", "兼职", "制造业", "商业", "失业中",
+        "学生", "工程师", "政府部门", "教育业", "服务行业",
+        "老板", "计算机业", "退休", "金融业",
+        "销售/广告/市场", NULL
+};
+
+static const gint choice_sizes[] = { 0, 13, 13, 5, 2, 7, 34, 15 };
+
+
+static const gchar *info_group_headers[] = {
+       QQ_MAIN_INFO,
+       QQ_EXTRA_INFO,
+       QQ_PERSONAL_INTRO,
+       QQ_MISC
+};
 
-// 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;
-	contact_info *ret_info;
-} qq_info_query;
+static const gchar **choices[] = {
+        NULL,
+        horoscope_names,
+        zodiac_names,
+        blood_types,
+        genders,
+        country_names,
+        province_names,
+        occupation_names
+};
+
+/*************** info and info_field methods *****************/
+
+// Given an id, return the template for that field.
+// Returns NULL if the id is not found.
+static const info_field *info_field_get_template(const gchar *id)
+{
+	const info_field *cur_field;
+	const gchar *cur_id;
+	
+	cur_field = info_template_data;
+	cur_id = cur_field->id;
+	while(cur_id != NULL) {
+		if (g_ascii_strcasecmp(cur_id, id) == 0) return cur_field;
+		cur_field++;
+		cur_id = cur_field->id;
+	}
+	gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Info field with id %s not found!", id);
+	return NULL;
+}
+
+// info_fields are compared by their group positions
+static gint info_field_compare(gconstpointer a, gconstpointer b, gpointer unused)
+{
+	return ((info_field *) a)->group_pos - ((info_field *) b)->group_pos;
+}
+
+static void info_field_free(info_field *i)
+{
+	g_free(i->value);
+	g_free(i);
+}
+
+// Parses the info_template_data above and returns a newly-allocated list
+// containing the desired fields from segments. This list is ordered by
+// group_pos.
+static GList *info_get_group(const gchar **info, const gchar *group_name)
+{
+	const info_field *cur;
+       	info_field *entry;
+	GList *group = NULL;
+
+	cur = info_template_data;
+	while (cur->id != NULL) {
+		if (g_ascii_strcasecmp(group_name, cur->group) == 0) {
+			entry = g_memdup(cur, sizeof(info_field));
+			entry->value = g_strdup(info[entry->pos]);
+			group = g_list_insert_sorted_with_data(group, entry, info_field_compare, NULL);
+		}
+		cur++;
+	}
+
+	return group;
+}
 
-/*****************************************************************************/
-// send a packet to get detailed information of uid,
-// if show_window, a info window will be display upon receiving a reply
+// determines if the given text value and choice group require
+// a lookup from the choice arrays
+static gboolean is_valid_index(gchar *value, gint choice)
+{
+	gint len, i;
+
+	if (choice == 0) return FALSE;
+	len = strlen(value);
+	// the server sends us an ascii index and none of arrays has more than 99
+	// elements
+	if (len > 3 || len == 0) return FALSE;
+	for (i = 0; i < len; i++)
+		if (!g_ascii_isdigit(value[i])) return FALSE;
+	i = atoi(value);
+	if (i < 0 || i >= choice_sizes[choice]) return FALSE; 
+	return TRUE;
+}
+
+// formats a field for printing
+static void append_field_to_str(gpointer field, gpointer str)
+{
+	info_field *f;
+	gint choice;
+	gboolean valid_index;
+	gchar *value;
+
+	f = (info_field *) field;
+	choice = f->choice;
+	valid_index = is_valid_index(f->value, choice);
+	if (choice && valid_index) value = g_strdup(choices[choice][atoi(f->value)]);
+	else value = qq_to_utf8(f->value, QQ_CHARSET_DEFAULT);
+	g_string_append_printf((GString *) str, "<b>%s:</b> %s<br />",
+			f->title, value);
+	g_free(value);
+	info_field_free(f);
+}
+
+// formats a group of information for printing
+static void append_group_to_str(GString *str, const gchar *group_name, const gchar **info)
+{
+	GList *group;
+
+	group = info_get_group(info, group_name);
+	g_string_append_printf(str, "<b>%s</b><br /><br />", (*(info_field *) group->data).group);
+	g_list_foreach(group, append_field_to_str, str);
+	g_list_free(group);
+	g_string_append_printf(str, "<br />");
+}
+
+// takes a contact_info struct and outputs the appropriate fields in
+// a printable format for our upcoming call to gaim_notify_userinfo
+static GString *info_to_str(const gchar **info)
+{
+	GString *info_text;
+
+	info_text = g_string_new("");
+	append_group_to_str(info_text, QQ_MAIN_INFO, info);
+	append_group_to_str(info_text, QQ_EXTRA_INFO, info);
+	append_group_to_str(info_text, QQ_PERSONAL_INTRO, info);
+	//if (QQ_DEBUG) append_group_to_str(info_text, QQ_MISC, info);
+
+	return info_text;
+}
+
+/*************** packets and UI management *****************/
+
+// send a packet to get detailed information of uid
 void qq_send_packet_get_info(GaimConnection * gc, guint32 uid, gboolean show_window)
 {
 	qq_data *qd;
 	gchar *uid_str;
-	GList *list;
 	qq_info_query *query;
-	gboolean is_exist;
-	contact_info_window *info_window;
 
 	g_return_if_fail(gc != NULL && gc->proto_data != NULL && uid != 0);
 
 	qd = (qq_data *) gc->proto_data;
 	uid_str = g_strdup_printf("%d", uid);
-	qq_send_cmd(gc, QQ_CMD_GET_USER_INFO, TRUE, 0, TRUE, uid_str, strlen(uid_str));
-
-	if (show_window) {	// prepare the window
-		is_exist = FALSE;	// see if there is already a window for this uid
-		list = qd->contact_info_window;
-		while (list != NULL) {
-			info_window = (contact_info_window *) list->data;
-			if (uid == info_window->uid) {
-				is_exist = TRUE;
-				break;
-			} else
-				list = list->next;
-		}		// while list
-		if (!is_exist) {	// create a new one
-			info_window = g_new0(contact_info_window, 1);
-			info_window->uid = uid;
-			qd->contact_info_window = g_list_append(qd->contact_info_window, info_window);
-		}		// if !is_exist
-	}			// if show_window
+	qq_send_cmd(gc, QQ_CMD_GET_USER_INFO, TRUE, 0, TRUE, (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);
 
 	g_free(uid_str);
-}				// qq_send_packet_get_info
+}
+
+// set up the fields requesting personal information and send a get_info packet
+// for myself
+void qq_prepare_modify_info(GaimConnection *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;
+	}
+}
+
 // send packet to modify personal information, and/or change password
-void qq_send_packet_modify_info(GaimConnection * gc, contact_info * info, gchar * new_passwd)
+void qq_send_packet_modify_info(GaimConnection *gc, contact_info *info, gchar *new_passwd)
 {
 	GaimAccount *a;
 	gchar *old_passwd, *info_field[QQ_CONTACT_FIELDS];
@@ -112,26 +338,174 @@
 
 	if (new_passwd == NULL || strlen(new_passwd) == 0)
 		create_packet_b(raw_data, &cursor, bar);
-	else {			// we gonna change passwd
-		create_packet_data(raw_data, &cursor, old_passwd, strlen(old_passwd));
+	else {			// we're gonna change passwd
+		create_packet_data(raw_data, &cursor, (guint8 *) old_passwd, strlen(old_passwd));
 		create_packet_b(raw_data, &cursor, bar);
-		create_packet_data(raw_data, &cursor, new_passwd, strlen(new_passwd));
+		create_packet_data(raw_data, &cursor, (guint8 *) new_passwd, strlen(new_passwd));
 	}
 
 	// 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, info_field[i], strlen(info_field[i]));
+		create_packet_data(raw_data, &cursor, (guint8 *) info_field[i], strlen(info_field[i]));
 	}
 	create_packet_b(raw_data, &cursor, bar);
 
 	qq_send_cmd(gc, QQ_CMD_UPDATE_INFO, TRUE, 0, TRUE, raw_data, cursor - raw_data);
 
-}				// qq_send_packet_modify_info
+}
+
+static void modify_info_cancel_cb(modify_info_data *mid)
+{
+	g_list_free(mid->misc);
+	g_free(mid);
+}
+
+// runs through all of the fields in the modify info UI and put
+// their values into the outgoing packet
+static void parse_field(gpointer field, gpointer outgoing_info)
+{
+	GaimRequestField *f;
+	gchar **segments, *value;
+	const info_field *ft;
+	const gchar *id;
+
+	f = (GaimRequestField *) field;
+	segments = (gchar **) outgoing_info;
+	id = gaim_request_field_get_id(f);
+	ft = info_field_get_template(id);
+	if (ft->choice && !ft->customizable) 
+		value = g_strdup_printf("%d", gaim_request_field_choice_get_value(f));
+	else {
+		value = (gchar *) gaim_request_field_string_get_value(f);
+		if (value == NULL) value = g_strdup("");
+		else value = utf8_to_qq(value, QQ_CHARSET_DEFAULT);
+	}
+	segments[ft->pos] = value;
+}
+
+// dumps the uneditable information straight into the outgoing packet 
+static void parse_misc_field(gpointer field, gpointer outgoing_info)
+{
+	info_field *f;
+	gchar **segments;
+
+	f = (info_field *) field;
+	segments = (gchar **) outgoing_info;
+	segments[f->pos] = g_strdup(f->value);
+	info_field_free(f);
+}
+
+// runs through all of the information fields and copies them into an
+// outgoing packet, then sends that packet
+static void modify_info_ok_cb(modify_info_data *mid, GaimRequestFields *fields)
+{
+	GaimConnection *gc;
+	GList *list,  *groups, *group_node;
+	gchar *info_field[QQ_CONTACT_FIELDS];
+	contact_info *info;
+	gint i;
+
+	gc = mid->gc;
+	list = mid->misc;
+	g_list_foreach(list, parse_misc_field, info_field);
+	g_list_free(list);
+	groups = gaim_request_fields_get_groups(fields);
+	while(groups) {
+		group_node = groups;
+		list = gaim_request_field_group_get_fields(group_node->data);
+		g_list_foreach(list, parse_field, info_field);
+		groups = g_list_remove_link(groups, group_node);
+	}
+	info = (contact_info *) info_field;
+
+	qq_send_packet_modify_info(gc, info, NULL);
+	g_free(mid);
+	for (i = 0; i < QQ_CONTACT_FIELDS; i++)
+		g_free(info_field[i]);
+}
 
-/*****************************************************************************/
-// process the reply of modidy_info packet
-void qq_process_modify_info_reply(guint8 * buf, gint buf_len, GaimConnection * gc)
+// Sets up the display for one group of information. This includes
+// managing which fields in the UI should be textfields and
+// which choices, and also mapping ints to choice values when appropriate.
+static void setup_group(gpointer field, gpointer group)
+{
+	info_field *f;
+	GaimRequestFieldGroup *g;
+	GaimRequestField *rf;
+	gint choice, index, j;
+	gboolean customizable, valid_index, multiline;
+	gchar *id, *value;
+
+	f = (info_field *) field;
+	g = (GaimRequestFieldGroup *) group;
+	choice = f->choice;
+	customizable = f->customizable;
+	id = f->id;
+	valid_index = TRUE;
+
+	if (!choice || customizable) {
+		valid_index = is_valid_index(f->value, choice);
+		multiline = id == "intro";
+		if (valid_index) {
+			index = atoi(f->value);
+			value = (gchar *) choices[choice][index];
+		} else value = qq_to_utf8(f->value, QQ_CHARSET_DEFAULT);
+		rf = gaim_request_field_string_new(id, f->title, value, multiline);
+	} else {
+		index = atoi(f->value);
+		value = (gchar *) choices[choice][index];
+		rf = gaim_request_field_choice_new(id, f->title, index);
+		j = 0;
+		while(choices[choice][j] != NULL)
+			gaim_request_field_choice_add(rf, choices[choice][j++]);
+	}
+	gaim_request_field_group_add_field(g, rf);
+	if (!valid_index) g_free(value);
+	info_field_free(f);
+}
+
+// Takes the info returned by a get_info packet for the user and sets up
+// a form using those values and the info_template.
+static void create_modify_info_dialogue(GaimConnection *gc, const gchar **info)
+{
+	GaimRequestFields *fields;
+	GaimRequestFieldGroup *group;
+	GaimRequestField *field;
+	GList *group_list;
+	modify_info_data *mid;
+	gint i;
+
+	fields = gaim_request_fields_new();
+	
+	// we only care about the first 3 groups, not the miscellaneous stuff
+	for (i = 0; i < 3; i++) {
+		group = gaim_request_field_group_new(info_group_headers[i]);
+		gaim_request_fields_add_group(fields, group);
+		group_list = info_get_group(info, info_group_headers[i]);
+		g_list_foreach(group_list, setup_group, group);
+		g_list_free(group_list);
+	}
+
+	//set this manually here instead of generating a new template column
+	field = gaim_request_fields_get_field(fields, "uid");
+	gaim_request_field_string_set_editable(field, FALSE);
+
+	//we need to pass the info that doesn't get modified as aux data
+	//because we'll still need it when we send the modify_info packet
+	mid = g_new0(modify_info_data, 1);
+	mid->gc = gc;
+	mid->misc = info_get_group(info, info_group_headers[3]);
+	
+	gaim_request_fields(gc, _("Modify my information"),
+			_("Modify my information"), NULL, fields,
+			_("Update my information"), G_CALLBACK(modify_info_ok_cb),
+			_("Cancel"), G_CALLBACK(modify_info_cancel_cb),
+			mid);
+}
+
+// process the reply of modify_info packet
+void qq_process_modify_info_reply(guint8 *buf, gint buf_len, GaimConnection *gc)
 {
 	qq_data *qd;
 	gint len;
@@ -145,18 +519,17 @@
 	data = g_newa(guint8, len);
 
 	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
-		if (qd->uid == atoi(data)) {	// return should be my uid
+		if (qd->uid == atoi((gchar *) data)) {	// return should be my uid
 			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Update info ACK OK\n");
-			gaim_notify_info(gc, NULL, _("You information have been updated"), NULL);
+			gaim_notify_info(gc, NULL, _("Your information has been updated"), NULL);
 		}
 	} else
 		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt modify info reply\n");
 
-}				// qq_process_modify_info_reply
+}
 
-/*****************************************************************************/
 // after getting info or modify myself, refresh the buddy list accordingly
-void qq_refresh_buddy_and_myself(contact_info * info, GaimConnection * gc)
+void qq_refresh_buddy_and_myself(contact_info *info, GaimConnection *gc)
 {
 	GaimBuddy *b;
 	qq_data *qd;
@@ -182,13 +555,16 @@
 		if (alias_utf8 != NULL)
 			q_bud->nickname = g_strdup(alias_utf8);
 		qq_update_buddy_contact(gc, q_bud);
-	}			// if q_bud
+	}
 	g_free(alias_utf8);
-}				// qq_refresh_buddy_and_myself
+}
 
-/*****************************************************************************/
+// XXX When we don't have any immediate response, we send duplicate get info packets 
+// to the server. If the server ends up responding to multiple packets, we get multiple
+// modify info dialogues, which is annoying. Fix this.
+
 // process reply to get_info packet
-void qq_process_get_info_reply(guint8 * buf, gint buf_len, GaimConnection * gc)
+void qq_process_get_info_reply(guint8 *buf, gint buf_len, GaimConnection *gc)
 {
 	gint len;
 	guint8 *data;
@@ -196,9 +572,8 @@
 	qq_info_query *query;
 	qq_data *qd;
 	contact_info *info;
-	contact_info_window *info_window;
-	gboolean show_window;
 	GList *list, *query_list;
+	GString *info_text;
 
 	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
 	g_return_if_fail(buf != NULL && buf_len != 0);
@@ -217,42 +592,30 @@
 		qq_refresh_buddy_and_myself(info, gc);
 
 		query_list = qd->info_query;
-		show_window = FALSE;
-		while (query_list != NULL) {
+		// ensure we're processing the right query
+		while (query_list) {
 			query = (qq_info_query *) query_list->data;
 			if (query->uid == atoi(info->uid)) {
-				show_window = query->show_window;
+				if (query->show_window) {
+					info_text = info_to_str((const gchar **) segments);
+					gaim_notify_userinfo(gc, info->uid, info_text->str, NULL, NULL);
+					g_string_free(info_text, TRUE);
+				} else if (query->modify_info) {
+					create_modify_info_dialogue(gc, (const gchar **) segments);
+				}
 				qd->info_query = g_list_remove(qd->info_query, qd->info_query->data);
 				g_free(query);
 				break;
 			}
 			query_list = query_list->next;
-		}		// while query_list
+		}
 
-		if (!show_window) {
-			g_strfreev(segments);
-			return;
-		}
-		// if not show_window, we can not find the window here either
-		list = qd->contact_info_window;
-		while (list != NULL) {
-			info_window = (contact_info_window *) (list->data);
-			if (info_window->uid == atoi(info->uid)) {
-				if (info_window->window)
-					qq_refresh_contact_info_dialog(info, gc, info_window);
-				else
-					qq_show_contact_info_dialog(info, gc, info_window);
-				break;
-			} else
-				list = list->next;
-		}		// while list
 		g_strfreev(segments);
 	} else
 		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt get info reply\n");
 
-}				// qq_process_get_info_reply
+}
 
-/*****************************************************************************/
 void qq_info_query_free(qq_data * qd)
 {
 	gint i;
@@ -268,7 +631,4 @@
 		i++;
 	}
 	gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d info queries are freed!\n", i);
-}				// qq_add_buddy_request_free
-
-/*****************************************************************************/
-// END OF FILE
+}