libpurple/protocols/qq/buddy_info.c

changeset 15435
4b933b06d75e
parent 15368
a82976ccee64
child 15498
2ee3112b6f24
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/qq/buddy_info.c	Sat Jan 20 02:32:10 2007 +0000
@@ -0,0 +1,836 @@
+/**
+ * @file buddy_info.c
+ *
+ * gaim
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "internal.h"
+#include "debug.h"
+#include "notify.h"
+#include "request.h"
+
+#include "utils.h"
+#include "packet_parse.h"
+#include "buddy_info.h"
+#include "char_conv.h"
+#include "crypt.h"
+#include "header_info.h"
+#include "keep_alive.h"
+#include "send_core.h"
+
+#define QQ_PRIMARY_INFORMATION _("Primary Information")
+#define QQ_ADDITIONAL_INFORMATION _("Additional Information")
+#define QQ_INTRO _("Personal Introduction")
+#define QQ_NUMBER _("QQ Number")
+#define QQ_NICKNAME _("Nickname")
+#define QQ_NAME _("Name")
+#define QQ_AGE _("Age")
+#define QQ_GENDER _("Gender")
+#define QQ_COUNTRY _("Country/Region")
+#define QQ_PROVINCE _("Province/State")
+#define QQ_CITY _("City")
+#define QQ_HOROSCOPE _("Horoscope Symbol")
+#define QQ_OCCUPATION _("Occupation")
+#define QQ_ZODIAC _("Zodiac Sign")
+#define QQ_BLOOD _("Blood Type")
+#define QQ_COLLEGE _("College")
+#define QQ_EMAIL _("Email")
+#define QQ_ADDRESS _("Address")
+#define QQ_ZIPCODE _("Zipcode")
+#define QQ_CELL _("Cellphone Number")
+#define QQ_TELEPHONE _("Phone Number")
+#define QQ_HOMEPAGE _("Homepage")
+
+#define QQ_HOROSCOPE_SIZE 13
+static const gchar *horoscope_names[] = {
+	"-", N_("Aquarius"), N_("Pisces"), N_("Aries"), N_("Taurus"),
+	N_("Gemini"), N_("Cancer"), N_("Leo"), N_("Virgo"), N_("Libra"),
+	N_("Scorpio"), N_("Sagittarius"), N_("Capricorn")
+};
+
+#define QQ_ZODIAC_SIZE 13
+static const gchar *zodiac_names[] = {
+	"-", N_("Rat"), N_("Ox"), N_("Tiger"), N_("Rabbit"),
+	N_("Dragon"), N_("Snake"), N_("Horse"), N_("Goat"), N_("Monkey"),
+	N_("Rooster"), N_("Dog"), N_("Pig")
+};
+
+#define QQ_BLOOD_SIZE 6
+static const gchar *blood_types[] = {
+	"-", "A", "B", "O", "AB", N_("Other")
+};
+
+#define QQ_GENDER_SIZE 2
+static const gchar *genders[] = {
+	N_("Male"),
+	N_("Female")
+};
+
+#define QQ_CONTACT_FIELDS                               37
+
+/* There is no user id stored in the reply packet for information query
+ * we have to manually store the query, so that we know the query source */
+typedef struct _qq_info_query {
+	guint32 uid;
+	gboolean show_window;
+	gboolean modify_info;
+} qq_info_query;
+
+/* We get an info packet on ourselves before we modify our information.
+ * Even though not all of the information is modifiable, it still
+ * all needs to be there when we send out the modify info packet */
+typedef struct _modify_info_data {
+	GaimConnection *gc;
+	contact_info *info;
+} modify_info_data;
+
+/* return -1 as a sentinel */
+static gint choice_index(const gchar *value, const gchar **choice, gint choice_size)
+{
+	gint len, i;
+
+	len = strlen(value);
+	if (len > 3 || len == 0) return -1;
+	for (i = 0; i < len; i++) {
+		if (!g_ascii_isdigit(value[i]))
+			return -1;
+	}
+	i = strtol(value, NULL, 10);
+	if (i >= choice_size)
+		return -1;
+
+	return i;
+}
+
+/* return should be freed */
+static gchar *field_value(const gchar *field, const gchar **choice, gint choice_size)
+{
+	gint index, len;
+
+	len = strlen(field);
+	if (len == 0) {
+		return NULL;
+	} else if (choice != NULL) {
+		/* some choice fields are also customizable */
+		index = choice_index(field, choice, choice_size);
+		if (index == -1) {
+			if (strcmp(field, "-") != 0) {
+				return qq_to_utf8(field, QQ_CHARSET_DEFAULT);
+			} else {
+				return NULL;
+			}
+		/* else ASCIIized index */
+		} else {
+			if (strcmp(choice[index], "-") != 0)
+				return g_strdup(choice[index]);
+			else
+				return NULL;
+		}
+	} else {
+		if (strcmp(field, "-") != 0) {
+			return qq_to_utf8(field, QQ_CHARSET_DEFAULT);
+		} else {
+			return NULL;
+		}
+	}
+}
+
+static gboolean append_field_value(GaimNotifyUserInfo *user_info, const gchar *field,
+		const gchar *title, const gchar **choice, gint choice_size)
+{
+	gchar *value = field_value(field, choice, choice_size);
+
+	if (value != NULL) {
+		gaim_notify_user_info_add_pair(user_info, title, value);
+		g_free(value);
+		
+		return TRUE;
+	}
+	
+	return FALSE;
+}
+
+static GaimNotifyUserInfo *
+info_to_notify_user_info(const contact_info *info)
+{
+	GaimNotifyUserInfo *user_info = gaim_notify_user_info_new();
+	const gchar *intro;
+	gboolean has_extra_info = FALSE;
+
+	gaim_notify_user_info_add_pair(user_info, QQ_NUMBER, info->uid);
+
+	append_field_value(user_info, info->nick, QQ_NICKNAME, NULL, 0);
+	append_field_value(user_info, info->name, QQ_NAME, NULL, 0);
+	append_field_value(user_info, info->age, QQ_AGE, NULL, 0);
+	append_field_value(user_info, info->gender, QQ_GENDER, genders, QQ_GENDER_SIZE);
+	append_field_value(user_info, info->country, QQ_COUNTRY, NULL, 0);
+	append_field_value(user_info, info->province, QQ_PROVINCE, NULL, 0);
+	append_field_value(user_info, info->city, QQ_CITY, NULL, 0);
+
+	gaim_notify_user_info_add_section_header(user_info, QQ_ADDITIONAL_INFORMATION);
+
+	has_extra_info |= append_field_value(user_info, info->horoscope, QQ_HOROSCOPE, horoscope_names, QQ_HOROSCOPE_SIZE);
+	has_extra_info |= append_field_value(user_info, info->occupation, QQ_OCCUPATION, NULL, 0);
+	has_extra_info |= append_field_value(user_info, info->zodiac, QQ_ZODIAC, zodiac_names, QQ_ZODIAC_SIZE);
+	has_extra_info |= append_field_value(user_info, info->blood, QQ_BLOOD, blood_types, QQ_BLOOD_SIZE);
+	has_extra_info |= append_field_value(user_info, info->college, QQ_COLLEGE, NULL, 0);
+	has_extra_info |= append_field_value(user_info, info->email, QQ_EMAIL, NULL, 0);
+	has_extra_info |= append_field_value(user_info, info->address, QQ_ADDRESS, NULL, 0);
+	has_extra_info |= append_field_value(user_info, info->zipcode, QQ_ZIPCODE, NULL, 0);
+	has_extra_info |= append_field_value(user_info, info->hp_num, QQ_CELL, NULL, 0);
+	has_extra_info |= append_field_value(user_info, info->tel, QQ_TELEPHONE, NULL, 0);
+	has_extra_info |= append_field_value(user_info, info->homepage, QQ_HOMEPAGE, NULL, 0);
+
+	if (!has_extra_info)
+		gaim_notify_user_info_remove_last_item(user_info);
+
+	intro = field_value(info->intro, NULL, 0);
+	if (intro) {
+		gaim_notify_user_info_add_pair(user_info, QQ_INTRO, intro);
+	}
+
+	/* for debugging */
+	/*
+	g_string_append_printf(info_text, "<br /><br /><b>%s</b><br />", "Miscellaneous");
+	append_field_value(info_text, info->pager_sn, "pager_sn", NULL, 0);
+	append_field_value(info_text, info->pager_num, "pager_num", NULL, 0);
+	append_field_value(info_text, info->pager_sp, "pager_sp", NULL, 0);
+	append_field_value(info_text, info->pager_base_num, "pager_base_num", NULL, 0);
+	append_field_value(info_text, info->pager_type, "pager_type", NULL, 0);
+	append_field_value(info_text, info->auth_type, "auth_type", NULL, 0);
+	append_field_value(info_text, info->unknown1, "unknown1", NULL, 0);
+	append_field_value(info_text, info->unknown2, "unknown2", NULL, 0);
+	append_field_value(info_text, info->face, "face", NULL, 0);
+	append_field_value(info_text, info->hp_type, "hp_type", NULL, 0);
+	append_field_value(info_text, info->unknown3, "unknown3", NULL, 0);
+	append_field_value(info_text, info->unknown4, "unknown4", NULL, 0);
+	append_field_value(info_text, info->unknown5, "unknown5", NULL, 0);
+	append_field_value(info_text, info->is_open_hp, "is_open_hp", NULL, 0);
+	append_field_value(info_text, info->is_open_contact, "is_open_contact", NULL, 0);
+	append_field_value(info_text, info->qq_show, "qq_show", NULL, 0);
+	append_field_value(info_text, info->unknown6, "unknown6", NULL, 0);
+	*/
+
+	return user_info;
+}
+
+/* send a packet to get detailed information of uid */
+void qq_send_packet_get_info(GaimConnection *gc, guint32 uid, gboolean show_window)
+{
+	qq_data *qd;
+	gchar uid_str[11];
+	qq_info_query *query;
+
+	g_return_if_fail(uid != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	g_snprintf(uid_str, sizeof(uid_str), "%d", uid);
+	qq_send_cmd(gc, QQ_CMD_GET_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);
+}
+
+/* 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 */
+static void qq_send_packet_modify_info(GaimConnection *gc, gchar **segments)
+{
+	gint i;
+	guint8 *raw_data, *cursor, bar;
+
+	g_return_if_fail(segments != NULL);
+
+	bar = 0x1f;
+	raw_data = g_newa(guint8, MAX_PACKET_SIZE - 128);
+	cursor = raw_data;
+
+	create_packet_b(raw_data, &cursor, bar);
+
+	/* important! skip the first uid entry */
+	for (i = 1; i < QQ_CONTACT_FIELDS; i++) {
+		create_packet_b(raw_data, &cursor, bar);
+		create_packet_data(raw_data, &cursor, (guint8 *) segments[i], strlen(segments[i]));
+	}
+	create_packet_b(raw_data, &cursor, bar);
+
+	qq_send_cmd(gc, QQ_CMD_UPDATE_INFO, TRUE, 0, TRUE, raw_data, cursor - raw_data);
+
+}
+
+static void modify_info_cancel_cb(modify_info_data *mid)
+{
+	qq_data *qd;
+
+	qd = (qq_data *) mid->gc->proto_data;
+	qd->modifying_info = FALSE;
+
+	g_strfreev((gchar **) mid->info);
+	g_free(mid);
+}
+
+static gchar *parse_field(GList **list, gboolean choice)
+{
+	gchar *value;
+	GaimRequestField *field;
+
+	field = (GaimRequestField *) (*list)->data;
+	if (choice) {
+		value = g_strdup_printf("%d", gaim_request_field_choice_get_value(field));
+	} else {
+		value = (gchar *) gaim_request_field_string_get_value(field);
+		if (value == NULL)
+			value = g_strdup("-");
+		else
+			value = utf8_to_qq(value, QQ_CHARSET_DEFAULT);
+	}
+	*list = g_list_remove_link(*list, *list);
+
+	return value;
+}
+
+/* parse fields and send info packet */
+static void modify_info_ok_cb(modify_info_data *mid, GaimRequestFields *fields)
+{
+	GaimConnection *gc;
+	qq_data *qd;
+	GList *list,  *groups;
+	contact_info *info;
+
+	gc = mid->gc;
+	qd = (qq_data *) gc->proto_data;
+	qd->modifying_info = FALSE;
+
+	info = mid->info;
+
+	groups = gaim_request_fields_get_groups(fields);
+	list = gaim_request_field_group_get_fields(groups->data);
+	info->uid = parse_field(&list, FALSE);
+	info->nick = parse_field(&list, FALSE);
+	info->name = parse_field(&list, FALSE);
+	info->age = parse_field(&list, FALSE);
+	info->gender = parse_field(&list, TRUE);
+	info->country = parse_field(&list, FALSE);
+	info->province = parse_field(&list, FALSE);
+	info->city = parse_field(&list, FALSE);
+	groups = g_list_remove_link(groups, groups);
+	list = gaim_request_field_group_get_fields(groups->data);
+	info->horoscope = parse_field(&list, TRUE);
+	info->occupation = parse_field(&list, FALSE);
+	info->zodiac = parse_field(&list, TRUE);
+	info->blood = parse_field(&list, TRUE);
+	info->college = parse_field(&list, FALSE);
+	info->email = parse_field(&list, FALSE);
+	info->address = parse_field(&list, FALSE);
+	info->zipcode = parse_field(&list, FALSE);
+	info->hp_num = parse_field(&list, FALSE);
+	info->tel = parse_field(&list, FALSE);
+	info->homepage = parse_field(&list, FALSE);
+	groups = g_list_remove_link(groups, groups);
+	list = gaim_request_field_group_get_fields(groups->data);
+	info->intro = parse_field(&list, FALSE);
+	groups = g_list_remove_link(groups, groups);
+
+	qq_send_packet_modify_info(gc, (gchar **) info);
+
+	g_strfreev((gchar **) mid->info);
+	g_free(mid);
+}
+
+static GaimRequestFieldGroup *setup_field_group(GaimRequestFields *fields, const gchar *title)
+{
+	GaimRequestFieldGroup *group;
+
+	group = gaim_request_field_group_new(title);
+	gaim_request_fields_add_group(fields, group);
+
+	return group;
+}
+
+static void add_string_field_to_group(GaimRequestFieldGroup *group,
+		const gchar *id, const gchar *title, const gchar *value)
+{
+	GaimRequestField *field;
+	gchar *utf8_value;
+
+	utf8_value = qq_to_utf8(value, QQ_CHARSET_DEFAULT);
+	field = gaim_request_field_string_new(id, title, utf8_value, FALSE);
+	gaim_request_field_group_add_field(group, field);
+	g_free(utf8_value);
+}
+
+static void add_choice_field_to_group(GaimRequestFieldGroup *group,
+		const gchar *id, const gchar *title, const gchar *value,
+		const gchar **choice, gint choice_size)
+{
+	GaimRequestField *field;
+	gint i, index;
+
+	index = choice_index(value, choice, choice_size);
+	field = gaim_request_field_choice_new(id, title, index);
+	for (i = 0; i < choice_size; i++)
+		gaim_request_field_choice_add(field, choice[i]);
+	gaim_request_field_group_add_field(group, field);
+}
+
+/* take the info returned by a get_info packet for myself and set up a request form */
+static void create_modify_info_dialogue(GaimConnection *gc, const contact_info *info)
+{
+	qq_data *qd;
+	GaimRequestFieldGroup *group;
+	GaimRequestFields *fields;
+	GaimRequestField *field;
+	modify_info_data *mid;
+
+	/* so we only have one dialog open at a time */
+	qd = (qq_data *) gc->proto_data;
+	if (!qd->modifying_info) {
+		qd->modifying_info = TRUE;
+
+		fields = gaim_request_fields_new();
+
+		group = setup_field_group(fields, QQ_PRIMARY_INFORMATION);
+		field = gaim_request_field_string_new("uid", QQ_NUMBER, info->uid, FALSE);
+		gaim_request_field_group_add_field(group, field);
+		gaim_request_field_string_set_editable(field, FALSE);
+		add_string_field_to_group(group, "nick", QQ_NICKNAME, info->nick);
+		add_string_field_to_group(group, "name", QQ_NAME, info->name);
+		add_string_field_to_group(group, "age", QQ_AGE, info->age);
+		add_choice_field_to_group(group, "gender", QQ_GENDER, info->gender, genders, QQ_GENDER_SIZE);
+		add_string_field_to_group(group, "country", QQ_COUNTRY, info->country);
+		add_string_field_to_group(group, "province", QQ_PROVINCE, info->province);
+		add_string_field_to_group(group, "city", QQ_CITY, info->city);
+		group = setup_field_group(fields, QQ_ADDITIONAL_INFORMATION);
+		add_choice_field_to_group(group, "horoscope", QQ_HOROSCOPE, info->horoscope, horoscope_names, QQ_HOROSCOPE_SIZE);
+		add_string_field_to_group(group, "occupation", QQ_OCCUPATION, info->occupation);
+		add_choice_field_to_group(group, "zodiac", QQ_ZODIAC, info->zodiac, zodiac_names, QQ_ZODIAC_SIZE);
+		add_choice_field_to_group(group, "blood", QQ_BLOOD, info->blood, blood_types, QQ_BLOOD_SIZE);
+		add_string_field_to_group(group, "college", QQ_COLLEGE, info->college);
+		add_string_field_to_group(group, "email", QQ_EMAIL, info->email);
+		add_string_field_to_group(group, "address", QQ_ADDRESS, info->address);
+		add_string_field_to_group(group, "zipcode", QQ_ZIPCODE, info->zipcode);
+		add_string_field_to_group(group, "hp_num", QQ_CELL, info->hp_num);
+		add_string_field_to_group(group, "tel", QQ_TELEPHONE, info->tel);
+		add_string_field_to_group(group, "homepage", QQ_HOMEPAGE, info->homepage);
+
+		group = setup_field_group(fields, QQ_INTRO);
+		field = gaim_request_field_string_new("intro", QQ_INTRO, info->intro, TRUE);
+		gaim_request_field_group_add_field(group, field);
+
+		/* prepare unmodifiable info */
+		mid = g_new0(modify_info_data, 1);
+		mid->gc = gc;
+		/* QQ_CONTACT_FIELDS+1 so that the array is NULL-terminated and can be g_strfreev()'ed later */
+		mid->info = (contact_info *) g_new0(gchar *, QQ_CONTACT_FIELDS+1);
+		mid->info->pager_sn = g_strdup(info->pager_sn);
+		mid->info->pager_num = g_strdup(info->pager_num);
+		mid->info->pager_sp = g_strdup(info->pager_sp);
+		mid->info->pager_base_num = g_strdup(info->pager_base_num);
+		mid->info->pager_type = g_strdup(info->pager_type);
+		mid->info->auth_type = g_strdup(info->auth_type);
+		mid->info->unknown1 = g_strdup(info->unknown1);
+		mid->info->unknown2 = g_strdup(info->unknown2);
+		mid->info->face = g_strdup(info->face);
+		mid->info->hp_type = g_strdup(info->hp_type);
+		mid->info->unknown3 = g_strdup(info->unknown3);
+		mid->info->unknown4 = g_strdup(info->unknown4);
+		mid->info->unknown5 = g_strdup(info->unknown5);
+		/* TODO stop hiding these 2 */
+		mid->info->is_open_hp = g_strdup(info->is_open_hp);
+		mid->info->is_open_contact = g_strdup(info->is_open_contact);
+		mid->info->qq_show = g_strdup(info->qq_show);
+		mid->info->unknown6 = g_strdup(info->unknown6);
+
+		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;
+	guint8 *data;
+
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		data[len] = '\0';
+		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, _("Your information has been updated"), NULL);
+		}
+	} else {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt modify info reply\n");
+	}
+}
+
+static void _qq_send_packet_modify_face(GaimConnection *gc, gint face_num)
+{
+	GaimAccount *account = gaim_connection_get_account(gc);
+	GaimPresence *presence = gaim_account_get_presence(account);
+	qq_data *qd = (qq_data *) gc->proto_data;
+	gint offset;
+
+	if(gaim_presence_is_status_primitive_active(presence, GAIM_STATUS_INVISIBLE)) {
+		offset = 2;
+	} else if(gaim_presence_is_status_primitive_active(presence, GAIM_STATUS_AWAY)
+			|| gaim_presence_is_status_primitive_active(presence, GAIM_STATUS_EXTENDED_AWAY)) {
+		offset = 1;
+	} else {
+		offset = 0;
+	}
+
+	qd->my_icon = 3 * (face_num - 1) + offset;
+	qd->modifying_face = TRUE;
+	qq_send_packet_get_info(gc, qd->uid, FALSE);
+}
+
+void qq_set_buddy_icon_for_user(GaimAccount *account, const gchar *who, const gchar *iconfile)
+{
+	FILE *file;
+	struct stat st;
+
+	g_return_if_fail(g_stat(iconfile, &st) == 0);
+	file = g_fopen(iconfile, "rb");
+	if (file) {
+		GaimBuddyIcon *icon;
+		size_t data_len;
+		gchar *data = g_new(gchar, st.st_size + 1);
+		data_len = fread(data, 1, st.st_size, file);
+		fclose(file);
+		gaim_buddy_icons_set_for_user(account, who, data, data_len);
+		icon = gaim_buddy_icons_find(account, who);
+		gaim_buddy_icon_set_path(icon, iconfile);
+	}
+}
+
+/* TODO: custom faces for QQ members and users with level >= 16 */
+void qq_set_my_buddy_icon(GaimConnection *gc, const gchar *iconfile)
+{
+	gchar *icon;
+	gint icon_num;
+	gint icon_len;
+	GaimAccount *account = gaim_connection_get_account(gc);
+	const gchar *icon_path = gaim_account_get_buddy_icon_path(account);
+	const gchar *buddy_icon_dir = qq_buddy_icon_dir();
+	gint prefix_len = strlen(QQ_ICON_PREFIX);
+	gint suffix_len = strlen(QQ_ICON_SUFFIX);
+	gint dir_len = strlen(buddy_icon_dir);
+	gchar *errmsg = g_strconcat(_("You are attempting to set a custom face. Gaim currently only allows the standard faces. Please choose an image from "), buddy_icon_dir, ".", NULL);
+	gboolean icon_global = gaim_account_get_bool(gc->account, "use-global-buddyicon", TRUE);
+
+	if (!icon_path)
+		icon_path = "";
+
+	icon_len = strlen(icon_path) - dir_len - 1 - prefix_len - suffix_len;
+
+	/* make sure we're using an appropriate icon */
+	if (!(g_ascii_strncasecmp(icon_path, buddy_icon_dir, dir_len) == 0
+		&& icon_path[dir_len] == G_DIR_SEPARATOR
+			&& g_ascii_strncasecmp(icon_path + dir_len + 1, QQ_ICON_PREFIX, prefix_len) == 0
+			&& g_ascii_strncasecmp(icon_path + dir_len + 1 + prefix_len + icon_len, QQ_ICON_SUFFIX, suffix_len) == 0
+			&& icon_len <= 3)) {
+		if (icon_global)
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "%s\n", errmsg);
+		else
+			gaim_notify_error(gc, _("Invalid QQ Face"), errmsg, NULL);
+		g_free(errmsg);
+		return;
+	}
+	/* strip everything but number */
+	icon = g_strndup(icon_path + dir_len + 1 + prefix_len, icon_len);
+	icon_num = strtol(icon, NULL, 10);
+	g_free(icon);
+	/* ensure face number in proper range */
+	if (icon_num > QQ_FACES) {
+		if (icon_global)
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "%s\n", errmsg);
+		else
+			gaim_notify_error(gc, _("Invalid QQ Face"), errmsg, NULL);
+		g_free(errmsg);
+		return;
+	}
+	g_free(errmsg);
+	/* tell server my icon changed */
+	_qq_send_packet_modify_face(gc, icon_num);
+	/* display in blist */
+	qq_set_buddy_icon_for_user(account, account->username, icon_path);
+}
+
+
+static void _qq_update_buddy_icon(GaimAccount *account, const gchar *name, gint face)
+{
+	gchar *icon_path;
+	GaimBuddyIcon *icon = gaim_buddy_icons_find(account, name);
+	gchar *icon_num_str = face_to_icon_str(face);
+	const gchar *old_path = gaim_buddy_icon_get_path(icon);
+	const gchar *buddy_icon_dir = qq_buddy_icon_dir();
+
+	icon_path = g_strconcat(buddy_icon_dir, G_DIR_SEPARATOR_S, QQ_ICON_PREFIX, 
+			icon_num_str, QQ_ICON_SUFFIX, NULL);
+	if (icon == NULL || old_path == NULL 
+		|| g_ascii_strcasecmp(icon_path, old_path) != 0)
+		qq_set_buddy_icon_for_user(account, name, icon_path);
+	g_free(icon_num_str);
+	g_free(icon_path);
+}
+
+/* after getting info or modify myself, refresh the buddy list accordingly */
+void qq_refresh_buddy_and_myself(contact_info *info, GaimConnection *gc)
+{
+	GaimBuddy *b;
+	qq_data *qd;
+	qq_buddy *q_bud;
+	gchar *alias_utf8, *gaim_name;
+	GaimAccount *account = gaim_connection_get_account(gc);
+
+	qd = (qq_data *) gc->proto_data;
+	gaim_name = uid_to_gaim_name(strtol(info->uid, NULL, 10));
+
+	alias_utf8 = qq_to_utf8(info->nick, QQ_CHARSET_DEFAULT);
+	if (qd->uid == strtol(info->uid, NULL, 10)) {	/* it is me */
+		qd->my_icon = strtol(info->face, NULL, 10);
+		if (alias_utf8 != NULL)
+			gaim_account_set_alias(account, alias_utf8);
+	}
+	/* update buddy list (including myself, if myself is the buddy) */
+	b = gaim_find_buddy(gc->account, gaim_name);
+	q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
+	if (q_bud != NULL) {	/* I have this buddy */
+		q_bud->age = strtol(info->age, NULL, 10);
+		q_bud->gender = strtol(info->gender, NULL, 10);
+		q_bud->face = strtol(info->face, NULL, 10);
+		if (alias_utf8 != NULL)
+			q_bud->nickname = g_strdup(alias_utf8);
+		qq_update_buddy_contact(gc, q_bud);
+		_qq_update_buddy_icon(gc->account, gaim_name, q_bud->face);
+	}
+	g_free(gaim_name);
+	g_free(alias_utf8);
+}
+
+/* process reply to get_info packet */
+void qq_process_get_info_reply(guint8 *buf, gint buf_len, GaimConnection *gc)
+{
+	gint len;
+	guint8 *data;
+	gchar **segments;
+	qq_info_query *query;
+	qq_data *qd;
+	contact_info *info;
+	GList *list, *query_list;
+	GaimNotifyUserInfo *user_info;
+
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	list = query_list = NULL;
+	len = buf_len;
+	data = g_newa(guint8, len);
+	info = NULL;
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		if (NULL == (segments = split_data(data, len, "\x1e", QQ_CONTACT_FIELDS)))
+			return;
+
+		info = (contact_info *) segments;
+		if (qd->modifying_face && strtol(info->face, NULL, 10) != qd->my_icon) {
+			gchar *icon = g_strdup_printf("%d", qd->my_icon);
+			qd->modifying_face = FALSE;
+			g_free(info->face);
+			info->face = icon;
+			qq_send_packet_modify_info(gc, segments);
+		}
+
+		qq_refresh_buddy_and_myself(info, gc);
+
+		query_list = qd->info_query;
+		/* ensure we're processing the right query */
+		while (query_list) {
+			query = (qq_info_query *) query_list->data;
+			if (query->uid == atoi(info->uid)) {
+				if (query->show_window) {
+					user_info = info_to_notify_user_info(info);
+					gaim_notify_userinfo(gc, info->uid, user_info, NULL, NULL);
+					gaim_notify_user_info_destroy(user_info);
+				} else if (query->modify_info) {
+					create_modify_info_dialogue(gc, info);
+				}
+				qd->info_query = g_list_remove(qd->info_query, qd->info_query->data);
+				g_free(query);
+				break;
+			}
+			query_list = query_list->next;
+		}
+
+		g_strfreev(segments);
+	} else {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt get info reply\n");
+	}
+}
+
+void qq_info_query_free(qq_data *qd)
+{
+	gint i;
+	qq_info_query *p;
+
+	g_return_if_fail(qd != NULL);
+
+	i = 0;
+	while (qd->info_query != NULL) {
+		p = (qq_info_query *) (qd->info_query->data);
+		qd->info_query = g_list_remove(qd->info_query, p);
+		g_free(p);
+		i++;
+	}
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d info queries are freed!\n", i);
+}
+
+void qq_send_packet_get_level(GaimConnection *gc, guint32 uid)
+{
+	guint8 buf[5];
+	guint32 tmp = g_htonl(uid);
+	buf[0] = 0;
+	memcpy(buf+1, &tmp, 4);
+	qq_send_cmd(gc, QQ_CMD_GET_LEVEL, TRUE, 0, TRUE, buf, 5);
+}
+
+void qq_send_packet_get_buddies_levels(GaimConnection *gc)
+{
+	guint8 *buf, *tmp, size;
+	qq_buddy *q_bud;
+	qq_data *qd = (qq_data *) gc->proto_data;
+	GList *node = qd->buddies;
+
+	if (qd->buddies) {
+		/* server only sends back levels for online buddies, no point
+ 	 	* in asking for anyone else */
+		size = 4*g_list_length(qd->buddies) + 1;
+		buf = g_new0(guint8, size);
+		tmp = buf + 1;
+
+		while (node != NULL) {
+			guint32 tmp4;
+			q_bud = (qq_buddy *) node->data;
+			if (q_bud != NULL) {
+				tmp4 = g_htonl(q_bud->uid);
+				memcpy(tmp, &tmp4, 4);
+				tmp += 4;
+			}
+			node = node->next;
+		}
+		qq_send_cmd(gc, QQ_CMD_GET_LEVEL, TRUE, 0, TRUE, buf, size);
+		g_free(buf);
+        }
+}
+
+void qq_process_get_level_reply(guint8 *buf, gint buf_len, GaimConnection *gc)
+{
+	guint32 uid, onlineTime;
+	guint16 level, timeRemainder;
+	gchar *gaim_name;
+	GaimBuddy *b;
+	qq_buddy *q_bud;
+	gint decr_len, i;
+	guint8 *decr_buf, *tmp;
+	GaimAccount *account = gaim_connection_get_account(gc);
+	qq_data *qd = (qq_data *) gc->proto_data;
+	
+	decr_len = buf_len;
+	decr_buf = g_new0(guint8, buf_len);
+	if (!qq_crypt(DECRYPT, buf, buf_len, qd->session_key, decr_buf, &decr_len)) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Couldn't decrypt get level packet\n");
+	}
+
+	decr_len--; 
+	if (decr_len % 12 != 0) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", 
+			"Get levels list of abnormal length. Truncating last %d bytes.\n", decr_len % 12);
+		decr_len -= (decr_len % 12);
+	}
+		
+	tmp = decr_buf + 1;
+	/* this byte seems random */
+	/*
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Byte one of get_level packet: %d\n", buf[0]);
+	*/
+	for (i = 0; i < decr_len; i += 12) {
+		uid = g_ntohl(*(guint32 *) tmp);
+		tmp += 4;
+		onlineTime = g_ntohl(*(guint32 *) tmp);
+		tmp += 4;
+		level = g_ntohs(*(guint16 *) tmp);
+		tmp += 2;
+		timeRemainder = g_ntohs(*(guint16 *) tmp);
+		tmp += 2;
+		/*
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "Level packet entry:\nuid: %d\nonlineTime: %d\nlevel: %d\ntimeRemainder: %d\n", 
+				uid, onlineTime, level, timeRemainder);
+		*/
+		gaim_name = uid_to_gaim_name(uid);
+		b = gaim_find_buddy(account, gaim_name);
+		q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
+
+		if (q_bud != NULL || uid == qd->uid) {
+			if (q_bud) {
+				q_bud->onlineTime = onlineTime;
+				q_bud->level = level;
+				q_bud->timeRemainder = timeRemainder;
+			}
+			if (uid == qd->uid) {
+				qd->my_level = level;
+			}
+		} else {
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ", 
+				"Got an online buddy %d, but not in my buddy list\n", uid);
+		}
+		g_free(gaim_name);
+	}
+	g_free(decr_buf);
+}

mercurial