| 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 } |
|