comparison libgaim/protocols/qq/buddy_info.c @ 14192:60b1bc8dbf37

[gaim-migrate @ 16863] Renamed 'core' to 'libgaim' committer: Tailor Script <tailor@pidgin.im>
author Evan Schoenberg <evan.s@dreskin.net>
date Sat, 19 Aug 2006 01:50:10 +0000
parents
children 584cbd1628d0
comparison
equal deleted inserted replaced
14191:009db0b357b5 14192:60b1bc8dbf37
1 /**
2 * The QQ2003C protocol plugin
3 *
4 * for gaim
5 *
6 * Copyright (C) 2004 Puzzlebird
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 */
22
23 #include "internal.h"
24 #include "debug.h"
25 #include "notify.h"
26 #include "request.h"
27
28 #include "utils.h"
29 #include "packet_parse.h"
30 #include "buddy_info.h"
31 #include "char_conv.h"
32 #include "crypt.h"
33 #include "header_info.h"
34 #include "keep_alive.h"
35 #include "send_core.h"
36
37 /* Below is all of the information necessary to reconstruct the various
38 * information fields that one can set in the official client. When we need
39 * to know about a specific field (e.g., should "city" be a choice
40 * or text field?), we can simply look it up from the template. Note that
41 * there are a number of unidentified fields. */
42 typedef struct _info_field {
43 gchar *title;
44 gchar *id; /* used by gaim_request fields */
45 gint pos;
46 gchar *group;
47 gint group_pos; /* for display order in the UI */
48 gint choice; /* indicates which character array contains the choices */
49 gboolean customizable; /* whether a user can enter any text as a value, regardless of choice arrays */
50 gchar *value;
51 } info_field;
52
53 static const info_field info_template_data[] = {
54 { N_("User ID"), "uid", 0, QQ_MAIN_INFO, 0, QQ_NO_CHOICE, TRUE, NULL },
55 { N_("Nickname"), "nick", 1, QQ_MAIN_INFO, 1, QQ_NO_CHOICE, TRUE, NULL },
56 { N_("Country/Region"), "country", 2, QQ_MAIN_INFO, 5, QQ_COUNTRY, TRUE, NULL },
57 { N_("Province/State"), "province", 3, QQ_MAIN_INFO, 6, QQ_PROVINCE, TRUE, NULL },
58 { N_("Zipcode"), "zipcode", 4, QQ_EXTRA_INFO, 7, QQ_NO_CHOICE, TRUE, NULL },
59 { N_("Address"), "address", 5, QQ_EXTRA_INFO, 6, QQ_NO_CHOICE, TRUE, NULL },
60 { N_("Phone Number"), "tel", 6, QQ_EXTRA_INFO, 9, QQ_NO_CHOICE, TRUE, NULL },
61 { N_("Age"), "age", 7, QQ_MAIN_INFO, 3, QQ_NO_CHOICE, TRUE, NULL },
62 { N_("Gender"), "gender", 8, QQ_MAIN_INFO, 4, QQ_GENDER, FALSE, NULL },
63 { N_("Name"), "name", 9, QQ_MAIN_INFO, 2, QQ_NO_CHOICE, TRUE, NULL },
64 { N_("Email"), "email", 10, QQ_EXTRA_INFO, 5, QQ_NO_CHOICE, TRUE, NULL },
65 { "pager_sn", "pager_sn", 11, QQ_MISC, 0, QQ_NO_CHOICE, TRUE, NULL },
66 { "pager_num", "pager_num", 12, QQ_MISC, 1, QQ_NO_CHOICE, TRUE, NULL },
67 { "pager_sp", "pager_sp", 13, QQ_MISC, 2, QQ_NO_CHOICE, TRUE, NULL },
68 { "pager_base_num", "pager_base_num", 14, QQ_MISC, 3, QQ_NO_CHOICE, TRUE, NULL },
69 { "pager_type", "pager_type", 15, QQ_MISC, 4, QQ_NO_CHOICE, TRUE, NULL },
70 { N_("Occupation"), "occupation", 16, QQ_EXTRA_INFO, 1, QQ_OCCUPATION, TRUE, NULL },
71 { N_("Homepage"), "homepage", 17, QQ_EXTRA_INFO, 10, QQ_NO_CHOICE, TRUE, NULL },
72 { "auth_type", "auth_type", 18, QQ_MISC, 5, QQ_NO_CHOICE, TRUE, NULL },
73 { "unknown1", "unknown1", 19, QQ_MISC, 6, QQ_NO_CHOICE, TRUE, NULL },
74 { "unknown2", "unknown2", 20, QQ_MISC, 7, QQ_NO_CHOICE, TRUE, NULL },
75 { "face", "face", 21, QQ_MISC, 8, QQ_NO_CHOICE, TRUE, NULL },
76 { N_("Cellphone Number"), "hp_num", 22, QQ_EXTRA_INFO, 8, QQ_NO_CHOICE, TRUE, NULL },
77 { "hp_type", "hp_type", 23, QQ_MISC, 9, QQ_NO_CHOICE, TRUE, NULL },
78 { N_("Personal Introduction"), "intro", 24, QQ_PERSONAL_INTRO, 0, QQ_NO_CHOICE, TRUE, NULL },
79 { N_("City"), "city", 25, QQ_MAIN_INFO, 7, QQ_NO_CHOICE, TRUE, NULL },
80 { "unknown3", "unknown3", 26, QQ_MISC, 10, QQ_NO_CHOICE, TRUE, NULL },
81 { "unknown4", "unknown4", 27, QQ_MISC, 11, QQ_NO_CHOICE, TRUE, NULL },
82 { "unknown5", "unknown5", 28, QQ_MISC, 12, QQ_NO_CHOICE, TRUE, NULL },
83 { "is_open_hp", "is_open_hp", 29, QQ_MISC, 13, QQ_NO_CHOICE, TRUE, NULL },
84 { "is_open_contact", "is_open_contact", 30, QQ_MISC, 14, QQ_NO_CHOICE, TRUE, NULL },
85 { N_("College"), "college", 31, QQ_EXTRA_INFO, 4, QQ_NO_CHOICE, TRUE, NULL },
86 { N_("Horoscope Symbol"), "horoscope", 32, QQ_EXTRA_INFO, 0, QQ_HOROSCOPE, FALSE, NULL },
87 { N_("Zodiac Symbol"), "zodiac", 33, QQ_EXTRA_INFO, 2, QQ_ZODIAC, FALSE, NULL },
88 { N_("Blood Type"), "blood", 34, QQ_EXTRA_INFO, 3, QQ_BLOOD, FALSE, NULL },
89 { "qq_show", "qq_show", 35, QQ_MISC, 15, QQ_NO_CHOICE, TRUE, NULL },
90 { "unknown6", "unknown6", 36, QQ_MISC, 16, QQ_NO_CHOICE, TRUE, NULL },
91 { NULL, NULL, 0, NULL, 0, 0, 0, NULL }
92 };
93
94 /* TODO: translate these arrays to their English equivalents
95 * and move these characters to the zh_CN po file */
96 static const gchar *horoscope_names[] = {
97 "-", "水瓶座", "双鱼座", "牡羊座", "金牛座",
98 "双子座", "巨蟹座", "狮子座", "处女座", "天秤座",
99 "天蝎座", "射手座", "魔羯座", NULL
100 };
101
102 static const gchar *zodiac_names[] = {
103 "-", "鼠", "牛", "虎", "兔",
104 "龙", "蛇", "马", "羊", "猴",
105 "鸡", "狗", "猪", NULL
106 };
107
108 static const gchar *blood_types[] = {
109 "-", N_("A"), N_("B"), N_("O"), N_("AB"), N_("Other"), NULL
110 };
111
112 static const gchar *genders[] = {
113 N_("Male"),
114 N_("Female"),
115 NULL
116 };
117
118 static const gchar *country_names[] = {
119 "中国", "中国香港", "中国澳门", "中国台湾",
120 "新加坡", "马来西亚", "美国", NULL
121 };
122
123 static const gchar *province_names[] = {
124 "北京", "天津", "上海", "重庆", "香港",
125 "河北", "山西", "内蒙古", "辽宁", "吉林",
126 "黑龙江", "江西", "浙江", "江苏", "安徽",
127 "福建", "山东", "河南", "湖北", "湖南",
128 "广东", "广西", "海南", "四川", "贵州",
129 "云南", "西藏", "陕西", "甘肃", "宁夏",
130 "青海", "新疆", "台湾", "澳门", NULL
131 };
132
133 static const gchar *occupation_names[] = {
134 "全职", "兼职", "制造业", "商业", "失业中",
135 "学生", "工程师", "政府部门", "教育业", "服务行业",
136 "老板", "计算机业", "退休", "金融业",
137 "销售/广告/市场", NULL
138 };
139
140 static const gint choice_sizes[] = { 0, 13, 13, 6, 2, 7, 34, 15 };
141
142
143 static const gchar *info_group_headers[] = {
144 QQ_MAIN_INFO,
145 QQ_EXTRA_INFO,
146 QQ_PERSONAL_INTRO,
147 QQ_MISC
148 };
149
150 static const gchar **choices[] = {
151 NULL,
152 horoscope_names,
153 zodiac_names,
154 blood_types,
155 genders,
156 country_names,
157 province_names,
158 occupation_names
159 };
160
161 /************************ info and info_field methods ************************/
162
163 /* Given an id, return the template for that field.
164 * Returns NULL if the id is not found. */
165 static const info_field *info_field_get_template(const gchar *id)
166 {
167 const info_field *cur_field;
168 const gchar *cur_id;
169
170 cur_field = info_template_data;
171 cur_id = cur_field->id;
172 while(cur_id != NULL) {
173 if (g_ascii_strcasecmp(cur_id, id) == 0)
174 return cur_field;
175 cur_field++;
176 cur_id = cur_field->id;
177 }
178 gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Info field with id %s not found!", id);
179 return NULL;
180 }
181
182 /* info_fields are compared by their group positions */
183 static gint info_field_compare(gconstpointer a, gconstpointer b)
184 {
185 return ((info_field *) a)->group_pos - ((info_field *) b)->group_pos;
186 }
187
188 static void info_field_free(info_field *i)
189 {
190 g_free(i->value);
191 g_free(i);
192 }
193
194 /* Parses the info_template_data above and returns a newly-allocated list
195 * containing the desired fields from segments. This list is ordered by
196 * group_pos. */
197 static GList *info_get_group(const gchar **info, const gchar *group_name)
198 {
199 const info_field *cur;
200 info_field *entry;
201 GList *group = NULL;
202
203 cur = info_template_data;
204 while (cur->id != NULL) {
205 if (g_ascii_strcasecmp(group_name, cur->group) == 0) {
206 entry = g_memdup(cur, sizeof(info_field));
207 entry->value = g_strdup(info[entry->pos]);
208 group = g_list_insert_sorted(group, entry, info_field_compare);
209 }
210 cur++;
211 }
212
213 return group;
214 }
215
216 /* Determines if the given text value and choice group require
217 * a lookup from the choice arrays. */
218 static gboolean is_valid_index(gchar *value, gint choice)
219 {
220 gint len, i;
221
222 if (choice == 0) return FALSE;
223 len = strlen(value);
224 /* the server sends us an ascii index and none of the arrays has more than 99
225 * elements */
226 if (len > 3 || len == 0) return FALSE;
227 for (i = 0; i < len; i++)
228 if (!g_ascii_isdigit(value[i]))
229 return FALSE;
230 i = atoi(value);
231 if (i < 0 || i >= choice_sizes[choice])
232 return FALSE;
233 return TRUE;
234 }
235
236 /* formats a field for printing */
237 static void append_field_to_str(gpointer field, gpointer str)
238 {
239 info_field *f;
240 gint choice;
241 gboolean valid_index;
242 gchar *value;
243
244 f = (info_field *) field;
245 choice = f->choice;
246 valid_index = is_valid_index(f->value, choice);
247 if (choice && valid_index) value = g_strdup(choices[choice][atoi(f->value)]);
248 else value = qq_to_utf8(f->value, QQ_CHARSET_DEFAULT);
249 g_string_append_printf((GString *) str, "<b>%s:</b> %s<br />",
250 f->title, value);
251 g_free(value);
252 info_field_free(f);
253 }
254
255 /* formats a group of information for printing */
256 static void append_group_to_str(GString *str, const gchar *group_name, const gchar **info)
257 {
258 GList *group;
259
260 group = info_get_group(info, group_name);
261 g_string_append_printf(str, "<b>%s</b><br /><br />", (*(info_field *) group->data).group);
262 g_list_foreach(group, append_field_to_str, str);
263 g_list_free(group);
264 g_string_append_printf(str, "<br />");
265 }
266
267 /* Takes a contact_info struct and outputs the appropriate fields in
268 * a printable format for our upcoming call to gaim_notify_userinfo. */
269 static GString *info_to_str(const gchar **info)
270 {
271 GString *info_text;
272
273 info_text = g_string_new("");
274 append_group_to_str(info_text, QQ_MAIN_INFO, info);
275 append_group_to_str(info_text, QQ_EXTRA_INFO, info);
276 append_group_to_str(info_text, QQ_PERSONAL_INTRO, info);
277 /* append_group_to_str(info_text, QQ_MISC, info); */
278
279 return info_text;
280 }
281
282 /************************ packets and UI management **************************/
283
284 /* send a packet to get detailed information of uid */
285 void qq_send_packet_get_info(GaimConnection * gc, guint32 uid, gboolean show_window)
286 {
287 qq_data *qd;
288 gchar *uid_str;
289 qq_info_query *query;
290
291 g_return_if_fail(gc != NULL && gc->proto_data != NULL && uid != 0);
292
293 qd = (qq_data *) gc->proto_data;
294 uid_str = g_strdup_printf("%d", uid);
295 qq_send_cmd(gc, QQ_CMD_GET_USER_INFO, TRUE, 0, TRUE, (guint8 *) uid_str, strlen(uid_str));
296
297 query = g_new0(qq_info_query, 1);
298 query->uid = uid;
299 query->show_window = show_window;
300 query->modify_info = FALSE;
301 qd->info_query = g_list_append(qd->info_query, query);
302
303 g_free(uid_str);
304 }
305
306 /* set up the fields requesting personal information and send a get_info packet
307 * for myself */
308 void qq_prepare_modify_info(GaimConnection *gc)
309 {
310 qq_data *qd;
311 GList *ql;
312 qq_info_query *query;
313
314 qd = (qq_data *) gc->proto_data;
315 qq_send_packet_get_info(gc, qd->uid, FALSE);
316 /* traverse backwards so we get the most recent info_query */
317 for (ql = g_list_last(qd->info_query); ql != NULL; ql = g_list_previous(ql)) {
318 query = ql->data;
319 if (query->uid == qd->uid)
320 query->modify_info = TRUE;
321 }
322 }
323
324 /* send packet to modify personal information */
325 void qq_send_packet_modify_info(GaimConnection *gc, contact_info *info)
326 {
327 gchar *info_field[QQ_CONTACT_FIELDS];
328 gint i;
329 guint8 *raw_data, *cursor, bar;
330
331 g_return_if_fail(gc != NULL && info != NULL);
332
333 bar = 0x1f;
334 raw_data = g_newa(guint8, MAX_PACKET_SIZE - 128);
335 cursor = raw_data;
336
337 g_memmove(info_field, info, sizeof(gchar *) * QQ_CONTACT_FIELDS);
338
339 create_packet_b(raw_data, &cursor, bar);
340
341 /* important!, skip the first uid entry */
342 for (i = 1; i < QQ_CONTACT_FIELDS; i++) {
343 create_packet_b(raw_data, &cursor, bar);
344 create_packet_data(raw_data, &cursor, (guint8 *) info_field[i], strlen(info_field[i]));
345 }
346 create_packet_b(raw_data, &cursor, bar);
347
348 qq_send_cmd(gc, QQ_CMD_UPDATE_INFO, TRUE, 0, TRUE, raw_data, cursor - raw_data);
349
350 }
351
352 static void modify_info_cancel_cb(modify_info_data *mid)
353 {
354 qq_data *qd;
355
356 qd = (qq_data *) mid->gc->proto_data;
357 qd->modifying_info = FALSE;
358
359 g_list_free(mid->misc);
360 g_free(mid);
361 }
362
363 /* Runs through all of the fields in the modify info UI and puts
364 * their values into the outgoing packet. */
365 static void parse_field(gpointer field, gpointer outgoing_info)
366 {
367 GaimRequestField *f;
368 gchar **segments, *value;
369 const info_field *ft;
370 const gchar *id;
371
372 f = (GaimRequestField *) field;
373 segments = (gchar **) outgoing_info;
374 id = gaim_request_field_get_id(f);
375 ft = info_field_get_template(id);
376 if (ft->choice && !ft->customizable) {
377 value = g_strdup_printf("%d", gaim_request_field_choice_get_value(f));
378 } else {
379 value = (gchar *) gaim_request_field_string_get_value(f);
380 if (value == NULL)
381 value = g_strdup("-");
382 else
383 value = utf8_to_qq(value, QQ_CHARSET_DEFAULT);
384 }
385 segments[ft->pos] = value;
386 }
387
388 /* dumps the uneditable information straight into the outgoing packet */
389 static void parse_misc_field(gpointer field, gpointer outgoing_info)
390 {
391 info_field *f;
392 gchar **segments;
393
394 f = (info_field *) field;
395 segments = (gchar **) outgoing_info;
396 segments[f->pos] = g_strdup(f->value);
397 info_field_free(f);
398 }
399
400 /* Runs through all of the information fields and copies them into an
401 * outgoing packet, then sends that packet. */
402 static void modify_info_ok_cb(modify_info_data *mid, GaimRequestFields *fields)
403 {
404 GaimConnection *gc;
405 qq_data *qd;
406 GList *list, *groups, *group_node;
407 gchar *info_field[QQ_CONTACT_FIELDS];
408 contact_info *info;
409 gint i;
410
411 gc = mid->gc;
412 qd = (qq_data *) gc->proto_data;
413 qd->modifying_info = FALSE;
414 list = mid->misc;
415 g_list_foreach(list, parse_misc_field, info_field);
416 g_list_free(list);
417 groups = gaim_request_fields_get_groups(fields);
418 while(groups) {
419 group_node = groups;
420 list = gaim_request_field_group_get_fields(group_node->data);
421 g_list_foreach(list, parse_field, info_field);
422 groups = g_list_remove_link(groups, group_node);
423 }
424 info = (contact_info *) info_field;
425
426 qq_send_packet_modify_info(gc, info);
427 g_free(mid);
428 for (i = 0; i < QQ_CONTACT_FIELDS; i++)
429 g_free(info_field[i]);
430 }
431
432 /* Sets up the display for one group of information. This includes
433 * managing which fields in the UI should be textfields and
434 * which choices, and also mapping ints to choice values when appropriate. */
435 static void setup_group(gpointer field, gpointer group)
436 {
437 info_field *f;
438 GaimRequestFieldGroup *g;
439 GaimRequestField *rf;
440 gint choice, index, j;
441 gboolean customizable, valid_index, multiline;
442 gchar *id, *value;
443
444 f = (info_field *) field;
445 g = (GaimRequestFieldGroup *) group;
446 choice = f->choice;
447 customizable = f->customizable;
448 id = f->id;
449 valid_index = TRUE;
450
451 if (!choice || customizable) {
452 valid_index = is_valid_index(f->value, choice);
453 multiline = id == "intro";
454 if (valid_index) {
455 index = atoi(f->value);
456 value = (gchar *) choices[choice][index];
457 } else {
458 value = qq_to_utf8(f->value, QQ_CHARSET_DEFAULT);
459 }
460 rf = gaim_request_field_string_new(id, f->title, value, multiline);
461 } else {
462 index = atoi(f->value);
463 value = (gchar *) choices[choice][index];
464 rf = gaim_request_field_choice_new(id, f->title, index);
465 j = 0;
466 while(choices[choice][j] != NULL)
467 gaim_request_field_choice_add(rf, choices[choice][j++]);
468 }
469 gaim_request_field_group_add_field(g, rf);
470 if (!valid_index)
471 g_free(value);
472 info_field_free(f);
473 }
474
475 /* Takes the info returned by a get_info packet for the user and sets up
476 * a form using those values and the info_template. */
477 static void create_modify_info_dialogue(GaimConnection *gc, const gchar **info)
478 {
479 qq_data *qd;
480 GaimRequestFields *fields;
481 GaimRequestFieldGroup *group;
482 GaimRequestField *field;
483 GList *group_list;
484 modify_info_data *mid;
485 gint i;
486
487 /* so we only have one dialog open at a time */
488 qd = (qq_data *) gc->proto_data;
489 if (!qd->modifying_info) {
490 qd->modifying_info = TRUE;
491
492 fields = gaim_request_fields_new();
493
494 /* we only care about the first 3 groups, not the miscellaneous stuff */
495 for (i = 0; i < 3; i++) {
496 group = gaim_request_field_group_new(info_group_headers[i]);
497 gaim_request_fields_add_group(fields, group);
498 group_list = info_get_group(info, info_group_headers[i]);
499 g_list_foreach(group_list, setup_group, group);
500 g_list_free(group_list);
501 }
502
503 field = gaim_request_fields_get_field(fields, "uid");
504 gaim_request_field_string_set_editable(field, FALSE);
505
506 mid = g_new0(modify_info_data, 1);
507 mid->gc = gc;
508 mid->misc = info_get_group(info, info_group_headers[3]);
509
510 gaim_request_fields(gc, _("Modify my information"),
511 _("Modify my information"), NULL, fields,
512 _("Update my information"), G_CALLBACK(modify_info_ok_cb),
513 _("Cancel"), G_CALLBACK(modify_info_cancel_cb),
514 mid);
515 }
516 }
517
518 /* process the reply of modify_info packet */
519 void qq_process_modify_info_reply(guint8 *buf, gint buf_len, GaimConnection *gc)
520 {
521 qq_data *qd;
522 gint len;
523 guint8 *data;
524
525 g_return_if_fail(gc != NULL && gc->proto_data != NULL);
526 g_return_if_fail(buf != NULL && buf_len != 0);
527
528 qd = (qq_data *) gc->proto_data;
529 len = buf_len;
530 data = g_newa(guint8, len);
531
532 if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
533 data[len] = '\0';
534 if (qd->uid == atoi((gchar *) data)) { /* return should be my uid */
535 gaim_debug(GAIM_DEBUG_INFO, "QQ", "Update info ACK OK\n");
536 gaim_notify_info(gc, NULL, _("Your information has been updated"), NULL);
537 }
538 } else {
539 gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt modify info reply\n");
540 }
541 }
542
543 /* after getting info or modify myself, refresh the buddy list accordingly */
544 void qq_refresh_buddy_and_myself(contact_info *info, GaimConnection *gc)
545 {
546 GaimBuddy *b;
547 qq_data *qd;
548 qq_buddy *q_bud;
549 gchar *alias_utf8;
550
551 g_return_if_fail(gc != NULL && gc->proto_data != NULL);
552 qd = (qq_data *) gc->proto_data;
553
554 alias_utf8 = qq_to_utf8(info->nick, QQ_CHARSET_DEFAULT);
555 if (qd->uid == strtol(info->uid, NULL, 10)) { /* it is me */
556 qd->my_icon = strtol(info->face, NULL, 10);
557 if (alias_utf8 != NULL)
558 gaim_account_set_alias(gc->account, alias_utf8);
559 }
560 /* update buddy list (including myself, if myself is the buddy) */
561 b = gaim_find_buddy(gc->account, uid_to_gaim_name(strtol(info->uid, NULL, 10)));
562 q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
563 if (q_bud != NULL) { /* I have this buddy */
564 q_bud->age = strtol(info->age, NULL, 10);
565 q_bud->gender = strtol(info->gender, NULL, 10);
566 q_bud->icon = strtol(info->face, NULL, 10);
567 if (alias_utf8 != NULL)
568 q_bud->nickname = g_strdup(alias_utf8);
569 qq_update_buddy_contact(gc, q_bud);
570 }
571 g_free(alias_utf8);
572 }
573
574 /* process reply to get_info packet */
575 void qq_process_get_info_reply(guint8 *buf, gint buf_len, GaimConnection *gc)
576 {
577 gint len;
578 guint8 *data;
579 gchar **segments;
580 qq_info_query *query;
581 qq_data *qd;
582 contact_info *info;
583 GList *list, *query_list;
584 GString *info_text;
585
586 g_return_if_fail(gc != NULL && gc->proto_data != NULL);
587 g_return_if_fail(buf != NULL && buf_len != 0);
588
589 qd = (qq_data *) gc->proto_data;
590 list = query_list = NULL;
591 len = buf_len;
592 data = g_newa(guint8, len);
593 info = NULL;
594
595 if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
596 if (NULL == (segments = split_data(data, len, "\x1e", QQ_CONTACT_FIELDS)))
597 return;
598
599 info = (contact_info *) segments;
600 qq_refresh_buddy_and_myself(info, gc);
601
602 query_list = qd->info_query;
603 /* ensure we're processing the right query */
604 while (query_list) {
605 query = (qq_info_query *) query_list->data;
606 if (query->uid == atoi(info->uid)) {
607 if (query->show_window) {
608 info_text = info_to_str((const gchar **) segments);
609 gaim_notify_userinfo(gc, info->uid, info_text->str, NULL, NULL);
610 g_string_free(info_text, TRUE);
611 } else if (query->modify_info) {
612 create_modify_info_dialogue(gc, (const gchar **) segments);
613 }
614 qd->info_query = g_list_remove(qd->info_query, qd->info_query->data);
615 g_free(query);
616 break;
617 }
618 query_list = query_list->next;
619 }
620
621 g_strfreev(segments);
622 } else {
623 gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt get info reply\n");
624 }
625 }
626
627 void qq_info_query_free(qq_data *qd)
628 {
629 gint i;
630 qq_info_query *p;
631
632 g_return_if_fail(qd != NULL);
633
634 i = 0;
635 while (qd->info_query != NULL) {
636 p = (qq_info_query *) (qd->info_query->data);
637 qd->info_query = g_list_remove(qd->info_query, p);
638 g_free(p);
639 i++;
640 }
641 gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d info queries are freed!\n", i);
642 }