# HG changeset patch # User Marcus Lundblad # Date 1225316333 0 # Node ID e17605cc2c0a41437a15e1623f862207b66c056e # Parent a4dfc631b140465eea64b4ed7b67d2b736833167# Parent b6dc71133eb85f120efbd5e98332a1267aa1479a propagate from branch 'im.pidgin.pidgin' (head 37145f1b4ec49b432ea571f96562d44f4c5a908e) to branch 'im.pidgin.cpw.malu.xmpp.attention' (head 06a2198ca8b2167d40d28ae761e461d104907fd9) diff -r a4dfc631b140 -r e17605cc2c0a NEWS --- a/NEWS Mon Oct 27 23:13:11 2008 +0000 +++ b/NEWS Wed Oct 29 21:38:53 2008 +0000 @@ -1,5 +1,7 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +Our development blog is available at: http://planet.pidgin.im + 2.5.2 (10/19/2008): Ethan: After a bit of a struggle with our services, which put this release off for an unfortunate length of time, we're diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/AUTHORS --- a/libpurple/protocols/qq/AUTHORS Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/AUTHORS Wed Oct 29 21:38:53 2008 +0000 @@ -35,4 +35,5 @@ khc@pidgin.im qulogic@pidgin.im rlaager@pidgin.im +Huang Guan : http://home.xxsyzx.com OpenQ Google Group : http://groups.google.com/group/openq diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/ChangeLog --- a/libpurple/protocols/qq/ChangeLog Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/ChangeLog Wed Oct 29 21:38:53 2008 +0000 @@ -1,3 +1,126 @@ +2008.10.28 - flos + * Updated AUTHORS + +2008.10.27 - ccpaging + * Fixed a bug in buddy_info.c + +2008.10.27 - ccpaging + * Update 'buddy_adding' protocol + +2008.10.22 - ccpaging + * 20081022 + +2008.10.20 - ccpaging + * Support incoming authorization of 'buddy_adding' protocol of QQ2007/2008 + +2008.10.14 - ccpaging + * 2007 remove buddy ok + * Removed group_search.c/h + +2008.10.10 - ccpaging + * Support part of 'buddy' protocol of QQ2007/2008 + +2008.10.10 - ccpaging + * Keep group_search.c/h for later use + * Update 'group' + +2008.10.09 - ccpaging + * 20081009-1 + +2008.10.09 - ccpaging + * Update 'group' protocol + * Functions of group_find, group_free, group_search merged into group_join and group_internal + * Removed group_find.c/h, group_free.c/h, group_search.c/h + +2008.10.08 - ccpaging + * Update 'group' protocol + +2008.10.08 - ccpaging + * 20081008-1 + +2008.10.08 - ccpaging + * Update group part + * Delete some meaningless functions and data + * Added 'change my icon' + +2008.10.08 - lonicerae + * Update Makefile.mingw + +2008.10.08 - ccpaging + * Fixed QQ_BUDDY_ICON_DIR problem + +2008.10.07 - lonicerae + * Update 'version display' + +2008.10.07 - lonicerae + * Added some defensive code for 'action' series functions of qq.c + +2008.10.07 - ccpaging + * Update buddy icon + +2008.10.07 - ccpaging + * Update qq_buddy + +2008.10.07 - ccpaging + * Update qun conversation + +2008.10.05 - lonicerae + * Bug fix in 'About OpenQ' dialog + +2008.10.05 - lonicerae + * Added 'About OpenQ' dialog + +2008.10.05 - ccpaging + * Add my uid into buddy list + * Fixed a minor bug in qq_create_buddy. Not get new buddy's info. + * There are 38 fields in protocol 2008, one more than 2005/2007. + * The packet of Modifing buddy info is changed. Need sample to fix it. + +2008.10.04 - ccpaging + * Update protocol for 2007 + * Code cleanup + +2008.10.04 - lonicerae + * fixed a bug in qq_base.c + +2008.10.03 - ccpaging + * 2007 protocol: + 1. fixed 'get room info' + 2. fixed 'get buddy level' + +2008.10.02 - ccpaging + * Added 'Captcha Display' function + * QQ2007 for openq, programed by Emil Alexiev: + 1. Most functions from patch written by Emil Alexiev merged into trunk, except 'buddy operations' + 2. 'online buddy status' and 'qun buddies' still have problems + * QQ2008 console client, programed by Shrimp: + 1. 'before login' and 'keep alive' parts merged into trunk + +2008.09.30 - ccpaging + * Successfully login using 2007/2008 protocols + +2008.09.29 - ccpaging + * 'Check Password' function for protocol 2007/2008 + +2008.09.28 - ccpaging + * The source is only for debug, not for user: + 1. Implement new QQ protocol 2007/2008, include login and change status + 2. Check 2005's login reply packet, get last 3 login time. + 3. Server's notice and news is displayed in self buddy (The new buddy created in buddy list). + 4. The notice messages when adding/removing QQ Qun's buddy displayed in char conversation. They are displayed as purple notify windows in the past. + 5. The notice messages when adding/removing buddy displayed in self buddy's conversation. They are displayed as purple notify windows in the past. + 6. Client version can be selected in account option. Now only qq2005 is working, other new version is only for debug. + +2008.09.26 - ccpaging + * Added 'Request/Add/Remove Buddy' functions + +2008.09.19 - ccpaging + * Rewrite buddy modify info, there is a ticket for this problem + * Use ship32 to trans action code between request packet and reply packet process + +2008.09.15 - csyfek + * im.pidgin.pidgin.openq branch + 2008.09.05 - ccpaging * Filter chars 0x01-0x20 in nickname diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/Makefile.am --- a/libpurple/protocols/qq/Makefile.am Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/Makefile.am Wed Oct 29 21:38:53 2008 +0000 @@ -18,12 +18,6 @@ file_trans.h \ group.c \ group.h \ - group_conv.c \ - group_conv.h \ - group_find.c \ - group_find.h \ - group_free.c \ - group_free.h \ group_internal.c \ group_internal.h \ group_im.c \ @@ -34,10 +28,8 @@ group_join.h \ group_opt.c \ group_opt.h \ - group_search.c \ - group_search.h \ - header_info.c \ - header_info.h \ + qq_define.c \ + qq_define.h \ im.c \ im.h \ qq_process.c \ @@ -54,8 +46,6 @@ send_file.h \ qq_trans.c \ qq_trans.h \ - sys_msg.c \ - sys_msg.h \ utils.c \ utils.h @@ -63,6 +53,11 @@ libqq_la_LDFLAGS = -module -avoid-version +CURRENT_REVISION=$(shell \ + awk 'BEGIN {"grep node .hg_archival.txt" | getline rev; \ + rev=substr(rev,7,6); \ + print rev}') + if STATIC_QQ st = -DPURPLE_STATIC_PRPL @@ -84,4 +79,5 @@ -I$(top_builddir)/libpurple \ -DQQ_BUDDY_ICON_DIR=\"$(datadir)/pixmaps/purple/buddy_icons/qq\" \ $(DEBUG_CFLAGS) \ - $(GLIB_CFLAGS) + $(GLIB_CFLAGS) \ + -DOPENQ_VERSION=\"$(CURRENT_REVISION)\" diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/Makefile.mingw --- a/libpurple/protocols/qq/Makefile.mingw Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/Makefile.mingw Wed Oct 29 21:38:53 2008 +0000 @@ -6,7 +6,10 @@ PIDGIN_TREE_TOP := ../../.. include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak - +CURRENT_REVISION=$(shell \ + awk 'BEGIN {"grep node .hg_archival.txt" | getline rev; \ + rev=substr(rev,7,6); \ + print rev}') TARGET = libqq TYPE = PLUGIN @@ -16,6 +19,7 @@ DLL_INSTALL_DIR = $(PURPLE_INSTALL_DIR) else ifeq ($(TYPE),PLUGIN) + DEFINES += -DOPENQ_VERSION=\"$(CURRENT_REVISION)\" DLL_INSTALL_DIR = $(PURPLE_INSTALL_PLUGINS_DIR) endif endif @@ -46,16 +50,12 @@ qq_crypt.c \ file_trans.c \ group.c \ - group_conv.c \ - group_find.c \ - group_free.c \ group_internal.c \ group_im.c \ group_info.c \ group_join.c \ group_opt.c \ - group_search.c \ - header_info.c \ + qq_define.c \ im.c \ packet_parse.c \ qq.c \ @@ -64,7 +64,6 @@ qq_process.c \ qq_trans.c \ send_file.c \ - sys_msg.c \ utils.c OBJECTS = $(C_SRC:%.c=%.o) diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/buddy_info.c --- a/libpurple/protocols/qq/buddy_info.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/buddy_info.c Wed Oct 29 21:38:53 2008 +0000 @@ -32,33 +32,11 @@ #include "buddy_list.h" #include "buddy_info.h" #include "char_conv.h" -#include "header_info.h" +#include "im.h" +#include "qq_define.h" #include "qq_base.h" #include "qq_network.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"), @@ -78,223 +56,171 @@ "-", "A", "B", "O", "AB", N_("Other") }; -#define QQ_GENDER_SIZE 2 +#define QQ_PUBLISH_SIZE 3 +static const gchar *publish_types[] = { + N_("Visible"), N_("Firend Only"), N_("Private") +}; + +#define QQ_GENDER_SIZE 3 static const gchar *genders[] = { + N_("Private"), N_("Male"), - N_("Female") + N_("Female"), +}; + +static const gchar *genders_zh[] = { + N_("-"), + N_("\xc4\xd0"), + N_("\xc5\xae"), +}; + +#define QQ_FACES 134 +#define QQ_ICON_PREFIX "qq_" +#define QQ_ICON_SUFFIX ".png" + +enum { + QQ_INFO_UID = 0, QQ_INFO_NICK, QQ_INFO_COUNTRY, QQ_INFO_PROVINCE, QQ_INFO_ZIPCODE, + QQ_INFO_ADDR, QQ_INFO_TEL, QQ_INFO_AGE, QQ_INFO_GENDER, QQ_INFO_NAME, QQ_INFO_EMAIL, + QQ_INFO_PG_SN, QQ_INFO_PG_NUM, QQ_INFO_PG_SP, QQ_INFO_PG_BASE_NUM, QQ_INFO_PG_TYPE, + QQ_INFO_OCCU, QQ_INFO_HOME_PAGE, QQ_INFO_AUTH_TYPE, QQ_INFO_UNKNOW1, QQ_INFO_UNKNOW2, + QQ_INFO_FACE, QQ_INFO_MOBILE, QQ_INFO_MOBILE_TYPE, QQ_INFO_INTRO, QQ_INFO_CITY, + QQ_INFO_UNKNOW3, QQ_INFO_UNKNOW4, QQ_INFO_UNKNOW5, + QQ_INFO_IS_PUB_MOBILE, QQ_INFO_IS_PUB_CONTACT, QQ_INFO_COLLEGE, QQ_INFO_HOROSCOPE, + QQ_INFO_ZODIAC, QQ_INFO_BLOOD, QQ_INFO_SHOW, QQ_INFO_UNKNOW6, + QQ_INFO_LAST_2007, QQ_INFO_LAST, +}; + +enum { + QQ_FIELD_UNUSED = 0, QQ_FIELD_BASE, QQ_FIELD_EXT, QQ_FIELD_CONTACT, QQ_FIELD_ADDR +}; + +enum { + QQ_FIELD_LABEL = 0, QQ_FIELD_STRING, QQ_FIELD_MULTI, QQ_FIELD_BOOL, QQ_FIELD_CHOICE, }; -#define QQ_CONTACT_FIELDS 37 -#define QQ_FACES 100 - -/* 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; +typedef struct { + int iclass; + int type; + char *id; + char *text; + const gchar **choice; + int choice_size; +} QQ_FIELD_INFO; -typedef struct _contact_info { - gchar *uid; - gchar *nick; - gchar *country; - gchar *province; - gchar *zipcode; - gchar *address; - gchar *tel; - gchar *age; - gchar *gender; - gchar *name; - gchar *email; - gchar *pager_sn; - gchar *pager_num; - gchar *pager_sp; - gchar *pager_base_num; - gchar *pager_type; - gchar *occupation; - gchar *homepage; - gchar *auth_type; - gchar *unknown1; - gchar *unknown2; - gchar *face; - gchar *hp_num; - gchar *hp_type; - gchar *intro; - gchar *city; - gchar *unknown3; - gchar *unknown4; - gchar *unknown5; - gchar *is_open_hp; - gchar *is_open_contact; - gchar *college; - gchar *horoscope; - gchar *zodiac; - gchar *blood; - gchar *qq_show; - gchar *unknown6; /* always 0x2D */ -} contact_info; +static const QQ_FIELD_INFO field_infos[] = { + { QQ_FIELD_BASE, QQ_FIELD_STRING, "uid", N_("QQ Number"), NULL, 0 }, + { QQ_FIELD_BASE, QQ_FIELD_STRING, "nick", N_("Nickname"), NULL, 0 }, + { QQ_FIELD_ADDR, QQ_FIELD_STRING, "country", N_("Country/Region"), NULL, 0 }, + { QQ_FIELD_ADDR, QQ_FIELD_STRING, "province", N_("Province/State"), NULL, 0 }, + { QQ_FIELD_ADDR, QQ_FIELD_STRING, "zipcode", N_("Zipcode"), NULL, 0 }, + { QQ_FIELD_ADDR, QQ_FIELD_STRING, "address", N_("Address"), NULL, 0 }, + { QQ_FIELD_CONTACT, QQ_FIELD_STRING, "tel", N_("Phone Number"), NULL, 0 }, + { QQ_FIELD_BASE, QQ_FIELD_STRING, "age", N_("Age"), NULL, 0 }, + { QQ_FIELD_BASE, QQ_FIELD_CHOICE, "gender", N_("Gender"), genders, QQ_GENDER_SIZE }, + { QQ_FIELD_BASE, QQ_FIELD_STRING, "name", N_("Name"), NULL, 0 }, + { QQ_FIELD_CONTACT, QQ_FIELD_STRING, "email", N_("Email"), NULL, 0 }, + { QQ_FIELD_UNUSED, QQ_FIELD_STRING, "pg_sn", "Pager Serial Num", NULL, 0 }, + { QQ_FIELD_UNUSED, QQ_FIELD_STRING, "pg_num", "Pager Num", NULL, 0 }, + { QQ_FIELD_UNUSED, QQ_FIELD_STRING, "pg_sp", "Pager Serivce Provider", NULL, 0 }, + { QQ_FIELD_UNUSED, QQ_FIELD_STRING, "pg_sta", "Pager Station Num", NULL, 0 }, + { QQ_FIELD_UNUSED, QQ_FIELD_STRING, "pg_type", "Pager Type", NULL, 0 }, + { QQ_FIELD_BASE, QQ_FIELD_STRING, "occupation", N_("Occupation"), NULL, 0 }, + { QQ_FIELD_CONTACT, QQ_FIELD_STRING, "homepage", N_("Homepage"), NULL, 0 }, + { QQ_FIELD_BASE, QQ_FIELD_BOOL, "auth", N_("Authorize adding"), NULL, 0 }, + { QQ_FIELD_UNUSED, QQ_FIELD_STRING, "unknow1", "Unknow1", NULL, 0 }, + { QQ_FIELD_UNUSED, QQ_FIELD_STRING, "unknow2", "Unknow2", NULL, 0 }, + { QQ_FIELD_UNUSED, QQ_FIELD_STRING, "face", "Face", NULL, 0 }, + { QQ_FIELD_CONTACT, QQ_FIELD_STRING, "mobile", N_("Cellphone Number"), NULL, 0 }, + { QQ_FIELD_UNUSED, QQ_FIELD_STRING, "mobile_type","Cellphone Type", NULL, 0 }, + { QQ_FIELD_BASE, QQ_FIELD_MULTI, "intro", N_("Personal Introduction"), NULL, 0 }, + { QQ_FIELD_ADDR, QQ_FIELD_STRING, "city", N_("City/Area"), NULL, 0 }, + { QQ_FIELD_UNUSED, QQ_FIELD_STRING, "unknow3", "Unknow3", NULL, 0 }, + { QQ_FIELD_UNUSED, QQ_FIELD_STRING, "unknow4", "Unknow4", NULL, 0 }, + { QQ_FIELD_UNUSED, QQ_FIELD_STRING, "unknow5", "Unknow5", NULL, 0 }, + { QQ_FIELD_UNUSED, QQ_FIELD_CHOICE, "pub_mobile", N_("Publish Mobile"), publish_types, QQ_PUBLISH_SIZE }, + { QQ_FIELD_CONTACT, QQ_FIELD_CHOICE, "pub_contact", N_("Publish Contact"), publish_types, QQ_PUBLISH_SIZE }, + { QQ_FIELD_EXT, QQ_FIELD_STRING, "college", N_("College"), NULL, 0 }, + { QQ_FIELD_EXT, QQ_FIELD_CHOICE, "horoscope", N_("Horoscope"), horoscope_names, QQ_HOROSCOPE_SIZE }, + { QQ_FIELD_EXT, QQ_FIELD_CHOICE, "zodiac", N_("Zodiac"), zodiac_names, QQ_ZODIAC_SIZE }, + { QQ_FIELD_EXT, QQ_FIELD_CHOICE, "blood", N_("Blood"), blood_types, QQ_BLOOD_SIZE }, + { QQ_FIELD_UNUSED, QQ_FIELD_STRING, "qq_show", "QQ Show", NULL, 0 }, + { QQ_FIELD_UNUSED, QQ_FIELD_STRING, "unknow6", "Unknow6", NULL, 0 }, + { QQ_FIELD_UNUSED, QQ_FIELD_STRING, "LAST_2005", "LAST_2005", NULL, 0 } +}; + +typedef struct _modify_info_request { + PurpleConnection *gc; + int iclass; + gchar **segments; +} modify_info_request; -/* 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 { - PurpleConnection *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) +#ifdef DEBUG +static void info_debug(gchar **segments) { - 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; +#if 0 + int index; + gchar *utf8_str; + for (index = 0; segments[index] != NULL && index < QQ_INFO_LAST; index++) { + if (field_infos[index].type == QQ_FIELD_STRING + || field_infos[index].type == QQ_FIELD_LABEL + || field_infos[index].type == QQ_FIELD_MULTI + || index == QQ_INFO_GENDER) { + utf8_str = qq_to_utf8(segments[index], QQ_CHARSET_DEFAULT); + purple_debug_info("QQ_BUDDY_INFO", "%s: %s\n", field_infos[index].text, utf8_str); + g_free(utf8_str); + continue; + } + purple_debug_info("QQ_BUDDY_INFO", "%s: %s\n", field_infos[index].text, segments[index]); } - i = strtol(value, NULL, 10); - if (i >= choice_size) - return -1; - - return i; +#endif } +#endif -/* return should be freed */ -static gchar *field_value(const gchar *field, const gchar **choice, gint choice_size) +static void info_display_only(PurpleConnection *gc, gchar **segments) { - gint index, len; + PurpleNotifyUserInfo *user_info; + gchar *utf8_value; + int index; + int choice_num; + + user_info = purple_notify_user_info_new(); - 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; + for (index = 1; segments[index] != NULL && index < QQ_INFO_LAST; index++) { + if (field_infos[index].iclass == QQ_FIELD_UNUSED) { + continue; } - } else { - if (strcmp(field, "-") != 0) { - return qq_to_utf8(field, QQ_CHARSET_DEFAULT); - } else { - return NULL; + switch (field_infos[index].type) { + case QQ_FIELD_BOOL: + purple_notify_user_info_add_pair(user_info, field_infos[index].text, + strtol(segments[index], NULL, 10) ? _("True") : _("False")); + break; + case QQ_FIELD_CHOICE: + choice_num = strtol(segments[index], NULL, 10); + if (choice_num < 0 || choice_num >= field_infos[index].choice_size) choice_num = 0; + + purple_notify_user_info_add_pair(user_info, field_infos[index].text, field_infos[index].choice[choice_num]); + break; + case QQ_FIELD_LABEL: + case QQ_FIELD_STRING: + case QQ_FIELD_MULTI: + default: + if (strlen(segments[index]) != 0) { + utf8_value = qq_to_utf8(segments[index], QQ_CHARSET_DEFAULT); + purple_notify_user_info_add_pair(user_info, field_infos[index].text, utf8_value); + g_free(utf8_value); + } + break; } } -} -static gboolean append_field_value(PurpleNotifyUserInfo *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) { - purple_notify_user_info_add_pair(user_info, title, value); - g_free(value); - - return TRUE; - } - - return FALSE; -} - -static PurpleNotifyUserInfo * - info_to_notify_user_info(const contact_info *info) -{ - PurpleNotifyUserInfo *user_info = purple_notify_user_info_new(); - const gchar *intro; - gboolean has_extra_info = FALSE; - - purple_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); - - purple_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); + purple_notify_userinfo(gc, segments[0], user_info, NULL, NULL); - if (!has_extra_info) - purple_notify_user_info_remove_last_item(user_info); - - intro = field_value(info->intro, NULL, 0); - if (intro) { - purple_notify_user_info_add_pair(user_info, QQ_INTRO, intro); - } - - /* for debugging */ - /* - g_string_append_printf(info_text, "

%s
", "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(PurpleConnection *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_BUDDY_INFO, (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); + purple_notify_user_info_destroy(user_info); + g_strfreev(segments); } void qq_request_buddy_info(PurpleConnection *gc, guint32 uid, - gint update_class, guint32 ship32) + gint update_class, int action) { qq_data *qd; gchar raw_data[16] = {0}; @@ -304,418 +230,238 @@ qd = (qq_data *) gc->proto_data; g_snprintf(raw_data, sizeof(raw_data), "%d", uid); qq_send_cmd_mess(gc, QQ_CMD_GET_BUDDY_INFO, (guint8 *) raw_data, strlen(raw_data), - update_class, ship32); -} - -/* set up the fields requesting personal information and send a get_info packet - * for myself */ -void qq_prepare_modify_info(PurpleConnection *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; - } + update_class, action); } /* send packet to modify personal information */ -static void qq_send_packet_modify_info(PurpleConnection *gc, contact_info *info) +static void request_change_info(PurpleConnection *gc, gchar **segments) { gint bytes = 0; guint8 raw_data[MAX_PACKET_SIZE - 128] = {0}; guint8 bar; + gchar *join; - g_return_if_fail(info != NULL); + g_return_if_fail(segments != NULL); bar = 0x1f; bytes += qq_put8(raw_data + bytes, bar); + bytes += qq_put8(raw_data + bytes, 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])); - } - */ - /* uid */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->uid, strlen(info->uid)); - /* nick */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->nick, strlen(info->nick)); - /* country */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->country, strlen(info->country)); - /* province */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->province, strlen(info->province)); - /* zipcode */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->zipcode, strlen(info->zipcode)); - /* address */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->address, strlen(info->address)); - /* tel */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->tel, strlen(info->tel)); - /* age */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->age, strlen(info->age)); - /* gender */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->gender, strlen(info->gender)); - /* name */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->name, strlen(info->name)); - /* email */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->email, strlen(info->email)); - /* pager_sn */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->pager_sn, strlen(info->pager_sn)); - /* pager_num */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->pager_num, strlen(info->pager_num)); - /* pager_sp */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->pager_sp, strlen(info->pager_sp)); - /* pager_base_num */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->pager_base_num, strlen(info->pager_base_num)); - /* pager_type */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->pager_type, strlen(info->pager_type)); - /* occupation */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->occupation, strlen(info->occupation)); - /* homepage */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->homepage, strlen(info->homepage)); - /* auth_type */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->auth_type, strlen(info->auth_type)); - /* unknown1 */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->unknown1, strlen(info->unknown1)); - /* unknown2 */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->unknown2, strlen(info->unknown2)); - /* face */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->face, strlen(info->face)); - /* hp_num */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->hp_num, strlen(info->hp_num)); - /* hp_type */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->hp_type, strlen(info->hp_type)); - /* intro */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->intro, strlen(info->intro)); - /* city */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->city, strlen(info->city)); - /* unknown3 */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->unknown3, strlen(info->unknown3)); - /* unknown4 */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->unknown4, strlen(info->unknown4)); - /* unknown5 */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->unknown5, strlen(info->unknown5)); - /* is_open_hp */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->is_open_hp, strlen(info->is_open_hp)); - /* is_open_contact */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->is_open_contact, strlen(info->is_open_contact)); - /* college */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->college, strlen(info->college)); - /* horoscope */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->horoscope, strlen(info->horoscope)); - /* zodiac */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->zodiac, strlen(info->zodiac)); - /* blood */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->blood, strlen(info->blood)); - /* qq_show */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->qq_show, strlen(info->qq_show)); - /* unknown6 */ - bytes += qq_put8(raw_data + bytes, bar); - bytes += qq_putdata(raw_data + bytes, (guint8 *)info->unknown6, strlen(info->unknown6)); + join = g_strjoinv("\x1f", segments + 1); + bytes += qq_putdata(raw_data + bytes, (guint8 *)join, strlen(join)); + g_free(join); bytes += qq_put8(raw_data + bytes, bar); + /* qq_show_packet("request_modify_info", raw_data, bytes); */ qq_send_cmd(gc, QQ_CMD_UPDATE_INFO, raw_data, bytes); - -} - -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(PurpleRequestField *field, gboolean choice) +static void info_modify_cancel_cb(modify_info_request *info_request) { - gchar *value; - - if (choice) { - value = g_strdup_printf("%d", purple_request_field_choice_get_value(field)); - } else { - value = (gchar *) purple_request_field_string_get_value(field); - if (value == NULL) - value = g_strdup("-"); - else - value = utf8_to_qq(value, QQ_CHARSET_DEFAULT); - } - - return value; + g_strfreev(info_request->segments); + g_free(info_request); } /* parse fields and send info packet */ -static void modify_info_ok_cb(modify_info_data *mid, PurpleRequestFields *fields) +static void info_modify_ok_cb(modify_info_request *info_request, PurpleRequestFields *fields) { PurpleConnection *gc; qq_data *qd; - GList *groups; - contact_info *info; - - gc = mid->gc; - qd = (qq_data *) gc->proto_data; - qd->modifying_info = FALSE; - - info = mid->info; + gchar **segments; + int index; + const char *utf8_str; + gchar *value; + int choice_num; - groups = purple_request_fields_get_groups(fields); - while (groups != NULL) { - PurpleRequestFieldGroup *group = groups->data; - const char *g_name = purple_request_field_group_get_title(group); - GList *fields = purple_request_field_group_get_fields(group); - - if (g_name == NULL) - continue; + gc = info_request->gc; + g_return_if_fail(gc != NULL && info_request->gc); + qd = (qq_data *) gc->proto_data; + segments = info_request->segments; + g_return_if_fail(segments != NULL); - while (fields != NULL) { - PurpleRequestField *field = fields->data; - const char *f_id = purple_request_field_get_id(field); - - if (!strcmp(QQ_PRIMARY_INFORMATION, g_name)) { - - if (!strcmp(f_id, "uid")) - info->uid = parse_field(field, FALSE); - else if (!strcmp(f_id, "nick")) - info->nick = parse_field(field, FALSE); - else if (!strcmp(f_id, "name")) - info->name = parse_field(field, FALSE); - else if (!strcmp(f_id, "age")) - info->age = parse_field(field, FALSE); - else if (!strcmp(f_id, "gender")) - info->gender = parse_field(field, TRUE); - else if (!strcmp(f_id, "country")) - info->country = parse_field(field, FALSE); - else if (!strcmp(f_id, "province")) - info->province = parse_field(field, FALSE); - else if (!strcmp(f_id, "city")) - info->city = parse_field(field, FALSE); - - } else if (!strcmp(QQ_ADDITIONAL_INFORMATION, g_name)) { + for (index = 1; segments[index] != NULL && index < QQ_INFO_LAST; index++) { + if (field_infos[index].iclass == QQ_FIELD_UNUSED) { + continue; + } + if (!purple_request_fields_exists(fields, field_infos[index].id)) { + continue; + } + switch (field_infos[index].type) { + case QQ_FIELD_BOOL: + value = purple_request_fields_get_bool(fields, field_infos[index].id) + ? g_strdup("1") : g_strdup("0"); + g_free(segments[index]); + segments[index] = value; + break; + case QQ_FIELD_CHOICE: + choice_num = purple_request_fields_get_choice(fields, field_infos[index].id); + if (choice_num < 0 || choice_num >= field_infos[index].choice_size) choice_num = 0; - if (!strcmp(f_id, "horoscope")) - info->horoscope = parse_field(field, TRUE); - else if (!strcmp(f_id, "occupation")) - info->occupation = parse_field(field, FALSE); - else if (!strcmp(f_id, "zodiac")) - info->zodiac = parse_field(field, TRUE); - else if (!strcmp(f_id, "blood")) - info->blood = parse_field(field, TRUE); - else if (!strcmp(f_id, "college")) - info->college = parse_field(field, FALSE); - else if (!strcmp(f_id, "email")) - info->email = parse_field(field, FALSE); - else if (!strcmp(f_id, "address")) - info->address = parse_field(field, FALSE); - else if (!strcmp(f_id, "zipcode")) - info->zipcode = parse_field(field, FALSE); - else if (!strcmp(f_id, "hp_num")) - info->hp_num = parse_field(field, FALSE); - else if (!strcmp(f_id, "tel")) - info->tel = parse_field(field, FALSE); - else if (!strcmp(f_id, "homepage")) - info->homepage = parse_field(field, FALSE); + if (index == QQ_INFO_GENDER) { + /* QQ Server only recept gender in Chinese */ + value = g_strdup(genders_zh[choice_num]); + } else { + value = g_strdup_printf("%d", choice_num); + } + g_free(segments[index]); + segments[index] = value; + break; + case QQ_FIELD_LABEL: + case QQ_FIELD_STRING: + case QQ_FIELD_MULTI: + default: + utf8_str = purple_request_fields_get_string(fields, field_infos[index].id); + if (utf8_str == NULL) { + value = g_strdup("-"); + } else { + value = utf8_to_qq(utf8_str, QQ_CHARSET_DEFAULT); + if (value == NULL) value = g_strdup("-"); + } + g_free(segments[index]); + segments[index] = value; + break; + } + } + request_change_info(gc, segments); - } else if (!strcmp(QQ_INTRO, g_name)) { - - if (!strcmp(f_id, "intro")) - info->intro = parse_field(field, FALSE); - - } - - fields = fields->next; - } - - groups = groups->next; - } - - /* This casting looks like a horrible idea to me -DAA - * yes, rewritten -s3e - * qq_send_packet_modify_info(gc, (gchar **) info); - */ - qq_send_packet_modify_info(gc, info); - - g_strfreev((gchar **) mid->info); - g_free(mid); + g_strfreev(segments); + g_free(info_request); } -static PurpleRequestFieldGroup *setup_field_group(PurpleRequestFields *fields, const gchar *title) -{ - PurpleRequestFieldGroup *group; - - group = purple_request_field_group_new(title); - purple_request_fields_add_group(fields, group); - - return group; -} - -static void add_string_field_to_group(PurpleRequestFieldGroup *group, - const gchar *id, const gchar *title, const gchar *value) +static void field_request_new(PurpleRequestFieldGroup *group, gint index, gchar **segments) { PurpleRequestField *field; gchar *utf8_value; + int choice_num; + int i; - utf8_value = qq_to_utf8(value, QQ_CHARSET_DEFAULT); - field = purple_request_field_string_new(id, title, utf8_value, FALSE); - purple_request_field_group_add_field(group, field); - g_free(utf8_value); + g_return_if_fail(index >=0 && segments[index] != NULL && index < QQ_INFO_LAST); + + switch (field_infos[index].type) { + case QQ_FIELD_STRING: + case QQ_FIELD_MULTI: + utf8_value = qq_to_utf8(segments[index], QQ_CHARSET_DEFAULT); + if (field_infos[index].type == QQ_FIELD_STRING) { + field = purple_request_field_string_new( + field_infos[index].id, field_infos[index].text, utf8_value, FALSE); + } else { + field = purple_request_field_string_new( + field_infos[index].id, field_infos[index].text, utf8_value, TRUE); + } + purple_request_field_group_add_field(group, field); + g_free(utf8_value); + break; + case QQ_FIELD_BOOL: + field = purple_request_field_bool_new( + field_infos[index].id, field_infos[index].text, + strtol(segments[index], NULL, 10) ? TRUE : FALSE); + purple_request_field_group_add_field(group, field); + break; + case QQ_FIELD_CHOICE: + choice_num = strtol(segments[index], NULL, 10); + if (choice_num < 0 || choice_num >= field_infos[index].choice_size) choice_num = 0; + + if (index == QQ_INFO_GENDER && strlen(segments[index]) != 0) { + for (i = 0; i < QQ_GENDER_SIZE; i++) { + if (strcmp(segments[index], genders_zh[i]) == 0) { + choice_num = i; + } + } + } + field = purple_request_field_choice_new( + field_infos[index].id, field_infos[index].text, choice_num); + for (i = 0; i < field_infos[index].choice_size; i++) { + purple_request_field_choice_add(field, field_infos[index].choice[i]); + } + purple_request_field_group_add_field(group, field); + break; + case QQ_FIELD_LABEL: + default: + field = purple_request_field_label_new(field_infos[index].id, segments[index]); + purple_request_field_group_add_field(group, field); + break; + } } -static void add_choice_field_to_group(PurpleRequestFieldGroup *group, - const gchar *id, const gchar *title, const gchar *value, - const gchar **choice, gint choice_size) -{ - PurpleRequestField *field; - gint i, index; - - index = choice_index(value, choice, choice_size); - field = purple_request_field_choice_new(id, title, index); - for (i = 0; i < choice_size; i++) - purple_request_field_choice_add(field, choice[i]); - purple_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(PurpleConnection *gc, const contact_info *info) +static void info_modify_dialogue(PurpleConnection *gc, gchar **segments, int iclass) { qq_data *qd; PurpleRequestFieldGroup *group; PurpleRequestFields *fields; - PurpleRequestField *field; - modify_info_data *mid; + modify_info_request *info_request; + gchar *utf8_title, *utf8_prim; + int index; - /* 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 = purple_request_fields_new(); + /* Keep one dialog once a time */ + purple_request_close_with_handle(gc); - group = setup_field_group(fields, QQ_PRIMARY_INFORMATION); - field = purple_request_field_string_new("uid", QQ_NUMBER, info->uid, FALSE); - purple_request_field_group_add_field(group, field); - purple_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); + fields = purple_request_fields_new(); + group = purple_request_field_group_new(NULL); + purple_request_fields_add_group(fields, group); - 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); + for (index = 1; segments[index] != NULL && index < QQ_INFO_LAST; index++) { + if (field_infos[index].iclass != iclass) { + continue; + } + field_request_new(group, index, segments); + } - group = setup_field_group(fields, QQ_INTRO); - field = purple_request_field_string_new("intro", QQ_INTRO, info->intro, TRUE); - purple_request_field_group_add_field(group, field); + switch (iclass) { + case QQ_FIELD_CONTACT: + utf8_title = g_strdup(_("Modify Contact")); + utf8_prim = g_strdup_printf("%s for %s", _("Modify Contact"), segments[0]); + case QQ_FIELD_ADDR: + utf8_title = g_strdup(_("Modify Address")); + utf8_prim = g_strdup_printf("%s for %s", _("Modify Address"), segments[0]); + case QQ_FIELD_EXT: + utf8_title = g_strdup(_("Modify Extend Information")); + utf8_prim = g_strdup_printf("%s for %s", _("Modify Extend Information"), segments[0]); + break; + case QQ_FIELD_BASE: + default: + utf8_title = g_strdup(_("Modify Information")); + utf8_prim = g_strdup_printf("%s for %s", _("Modify Information"), segments[0]); + } - /* 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); + info_request = g_new0(modify_info_request, 1); + info_request->gc = gc; + info_request->iclass = iclass; + info_request->segments = segments; - purple_request_fields(gc, _("Modify information"), - _("Modify information"), NULL, fields, - _("Update information"), G_CALLBACK(modify_info_ok_cb), - _("Cancel"), G_CALLBACK(modify_info_cancel_cb), - purple_connection_get_account(gc), NULL, NULL, - mid); - } + purple_request_fields(gc, + utf8_title, + utf8_prim, + NULL, + fields, + _("Update"), G_CALLBACK(info_modify_ok_cb), + _("Cancel"), G_CALLBACK(info_modify_cancel_cb), + purple_connection_get_account(gc), NULL, NULL, + info_request); + + g_free(utf8_title); + g_free(utf8_prim); } /* process the reply of modify_info packet */ -void qq_process_modify_info_reply(guint8 *data, gint data_len, PurpleConnection *gc) +void qq_process_change_info(PurpleConnection *gc, guint8 *data, gint data_len) { qq_data *qd; - g_return_if_fail(data != NULL && data_len != 0); qd = (qq_data *) gc->proto_data; data[data_len] = '\0'; - if (qd->uid == atoi((gchar *) data)) { /* return should be my uid */ - purple_debug_info("QQ", "Update info ACK OK\n"); - purple_notify_info(gc, _("QQ Buddy"), _("Successed:"), _("Change buddy information.")); + if (qd->uid != atoi((gchar *) data)) { /* return should be my uid */ + purple_debug_info("QQ", "Failed Updating info\n"); + qq_got_attention(gc, _("Failed changing buddy information.")); } } -static void _qq_send_packet_modify_face(PurpleConnection *gc, gint face_num) +static void request_set_buddy_icon(PurpleConnection *gc, gint face_num) { PurpleAccount *account = purple_connection_get_account(gc); PurplePresence *presence = purple_account_get_presence(account); @@ -732,204 +478,276 @@ } 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(PurpleAccount *account, const gchar *who, const gchar *icon_num, const gchar *iconfile) -{ - gchar *data; - gsize len; - - if (!g_file_get_contents(iconfile, &data, &len, NULL)) { - g_return_if_reached(); - } else { - purple_buddy_icons_set_for_user(account, who, data, len, icon_num); - } -} - -/* TODO: custom faces for QQ members and users with level >= 16 */ -void qq_set_my_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img) -{ - gchar *icon; - gint icon_num; - gint icon_len; - PurpleAccount *account = purple_connection_get_account(gc); - const gchar *icon_path = purple_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 = buddy_icon_dir ? strlen(buddy_icon_dir) : 0; - gchar *errmsg = g_strdup_printf(_("Setting custom faces is not currently supported. Please choose an image from %s."), buddy_icon_dir ? buddy_icon_dir : "(null)"); - gboolean icon_global = purple_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 (buddy_icon_dir && !(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) - purple_debug_error("QQ", "%s\n", errmsg); - else - purple_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) - purple_debug_error("QQ", "%s\n", errmsg); - else - purple_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, icon_path); + qq_request_buddy_info(gc, qd->uid, 0, QQ_BUDDY_INFO_SET_ICON); } - -static void _qq_update_buddy_icon(PurpleAccount *account, const gchar *name, gint face) +void qq_change_icon_cb(PurpleConnection *gc, const char *filepath) { - PurpleBuddy *buddy; - gchar *icon_num_str = face_to_icon_str(face); - const gchar *old_icon_num = NULL; + gchar **segments; + const gchar *filename; + gint index; + gint face; + gchar *error; + + g_return_if_fail(filepath != NULL); - if ((buddy = purple_find_buddy(account, name))) - old_icon_num = purple_buddy_icons_get_checksum_for_user(buddy); + purple_debug_info("QQ", "Change my icon to %s\n", filepath); + segments = g_strsplit_set(filepath, G_DIR_SEPARATOR_S, 0); + +#if 0 + for (index = 0; segments[index] != NULL; index++) { + purple_debug_info("QQ", "Split to %s\n", segments[index]); + } +#endif - if ((old_icon_num == NULL || - strcmp(icon_num_str, old_icon_num)) && (qq_buddy_icon_dir() != NULL)) - { - gchar *icon_path; + index = g_strv_length(segments) - 1; + if (index < 0) { + g_strfreev(segments); + return; + } - icon_path = g_strconcat(qq_buddy_icon_dir(), G_DIR_SEPARATOR_S, - QQ_ICON_PREFIX, icon_num_str, - QQ_ICON_SUFFIX, NULL); + filename = segments[index]; + index = strcspn (filename, "0123456789"); + if (index < 0 || index >= strlen(filename)) { + error = g_strdup_printf(_("Can not get face number in file name (%s)"), filename); + purple_notify_error(gc, _("QQ Buddy"), _("Failed change icon"), error); + g_free(error); + return; + } + face = strtol(filename+index, NULL, 10); + purple_debug_info("QQ", "Set face to %d\n", face); - qq_set_buddy_icon_for_user(account, name, icon_num_str, icon_path); - g_free(icon_path); - } - g_free(icon_num_str); + request_set_buddy_icon(gc, face); + + g_strfreev(segments); } -/* after getting info or modify myself, refresh the buddy list accordingly */ -static void qq_refresh_buddy_and_myself(contact_info *info, PurpleConnection *gc) +void qq_set_custom_icon(PurpleConnection *gc, PurpleStoredImage *img) { - PurpleBuddy *b; - qq_data *qd; - qq_buddy *q_bud; - gchar *alias_utf8; - gchar *purple_name; PurpleAccount *account = purple_connection_get_account(gc); - - qd = (qq_data *) gc->proto_data; - purple_name = uid_to_purple_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) - purple_account_set_alias(account, alias_utf8); - } - /* update buddy list (including myself, if myself is the buddy) */ - b = purple_find_buddy(gc->account, purple_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, purple_name, q_bud->face); - } - g_free(purple_name); - g_free(alias_utf8); -} - -/* process reply to get_info packet */ -void qq_process_get_buddy_info(guint8 *data, gint data_len, PurpleConnection *gc) -{ + const gchar *icon_path = purple_account_get_buddy_icon_path(account); gchar **segments; - qq_info_query *query; - qq_data *qd; - contact_info *info; - GList *list, *query_list; - PurpleNotifyUserInfo *user_info; + gint index; - g_return_if_fail(data != NULL && data_len != 0); - - qd = (qq_data *) gc->proto_data; - list = query_list = NULL; - info = NULL; - - if (NULL == (segments = split_data(data, data_len, "\x1e", QQ_CONTACT_FIELDS))) - return; + g_return_if_fail(icon_path != NULL); - 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, (contact_info *)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); - purple_notify_userinfo(gc, info->uid, user_info, NULL, NULL); - purple_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; + /* Fixme: + * icon_path is always null + * purple_imgstore_get_filename is always new file + * QQ buddy may set custom icon if level is over 16 */ + purple_debug_info("QQ", "Change my icon to %s\n", icon_path); + segments = g_strsplit_set(icon_path, G_DIR_SEPARATOR_S, 0); + for (index = 0; segments[index] != NULL; index++) { + purple_debug_info("QQ", "Split to %s\n", segments[index]); } g_strfreev(segments); } -void qq_info_query_free(qq_data *qd) +gchar *qq_get_icon_name(gint face) +{ + gint icon; + gchar *icon_name; + + icon = face / 3 + 1; + if (icon < 1 || icon > QQ_FACES) { + icon = 1; + } + + icon_name = g_strdup_printf("%s%d%s", QQ_ICON_PREFIX, icon, QQ_ICON_SUFFIX); + return icon_name; +} + +gchar *qq_get_icon_path(gchar *icon_name) +{ + gchar *icon_path; + const gchar *icon_dir; +#ifdef _WIN32 + static char *dir = NULL; + if (dir == NULL) { + dir = g_build_filename(wpurple_install_dir(), "pixmaps", + "purple", "buddy_icons", "qq", NULL); + } +#endif + + icon_dir = purple_prefs_get_string("/plugins/prpl/qq/icon_dir"); + if ( icon_dir == NULL || strlen(icon_dir) == 0) { +#ifdef _WIN32 + icon_dir = dir; +#else + icon_dir = QQ_BUDDY_ICON_DIR; +#endif + } + icon_path = g_strdup_printf("%s%c%s", icon_dir, G_DIR_SEPARATOR, icon_name); + + return icon_path; +} + +static void update_buddy_icon(PurpleAccount *account, const gchar *who, gint face) { - gint count; - qq_info_query *p; + PurpleBuddy *buddy; + const gchar *icon_name_prev = NULL; + gchar *icon_name; + gchar *icon_path; + gchar *icon_file_content; + gsize icon_file_size; + + g_return_if_fail(account != NULL && who != NULL); + + purple_debug_info("QQ", "Update %s icon to %d\n", who, face); + + icon_name = qq_get_icon_name(face); + purple_debug_info("QQ", "icon file name is %s\n", icon_name); + + if ((buddy = purple_find_buddy(account, who))) { + icon_name_prev = purple_buddy_icons_get_checksum_for_user(buddy); + if (icon_name_prev != NULL) { + purple_debug_info("QQ", "Previous icon is %s\n", icon_name_prev); + } + } + if (icon_name_prev != NULL && !strcmp(icon_name, icon_name_prev)) { + purple_debug_info("QQ", "Icon is not changed\n"); + g_free(icon_name); + return; + } - g_return_if_fail(qd != NULL); + icon_path = qq_get_icon_path(icon_name); + if (icon_path == NULL) { + g_free(icon_name); + return; + } + + if (!g_file_get_contents(icon_path, &icon_file_content, &icon_file_size, NULL)) { + purple_debug_error("QQ", "Failed reading icon file %s\n", icon_path); + } else { + purple_buddy_icons_set_for_user(account, who, + icon_file_content, icon_file_size, icon_name); + } + g_free(icon_name); + g_free(icon_path); +} + +/* after getting info or modify myself, refresh the buddy list accordingly */ +static void update_buddy_info(PurpleConnection *gc, gchar **segments) +{ + PurpleBuddy *buddy; + qq_data *qd; + qq_buddy_data *bd; + guint32 uid; + gchar *who; + gchar *alias_utf8; + PurpleAccount *account = purple_connection_get_account(gc); + + qd = (qq_data *) gc->proto_data; + + uid = strtol(segments[QQ_INFO_UID], NULL, 10); + who = uid_to_purple_name(uid); - count = 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); - count++; + qq_filter_str(segments[QQ_INFO_NICK]); + alias_utf8 = qq_to_utf8(segments[QQ_INFO_NICK], QQ_CHARSET_DEFAULT); + if (uid == qd->uid) { /* it is me */ + purple_debug_info("QQ", "Got my info\n"); + qd->my_icon = strtol(segments[QQ_INFO_FACE], NULL, 10); + if (alias_utf8 != NULL) { + purple_account_set_alias(account, alias_utf8); + } + /* add me to buddy list */ + buddy = qq_buddy_find_or_new(gc, uid); + } else { + buddy = purple_find_buddy(gc->account, who); + } + + if (buddy == NULL || buddy->proto_data == NULL) { + g_free(who); + g_free(alias_utf8); + return; + } + + /* update buddy list (including myself, if myself is the buddy) */ + bd = (qq_buddy_data *)buddy->proto_data; + + bd->age = strtol(segments[QQ_INFO_AGE], NULL, 10); + bd->gender = strtol(segments[QQ_INFO_GENDER], NULL, 10); + bd->face = strtol(segments[QQ_INFO_FACE], NULL, 10); + if (alias_utf8 != NULL) { + if (bd->nickname) g_free(bd->nickname); + bd->nickname = g_strdup(alias_utf8); } - if (count > 0) { - purple_debug_info("QQ", "%d info queries are freed!\n", count); + bd->last_update = time(NULL); + + purple_blist_server_alias_buddy(buddy, bd->nickname); + + /* convert face num from packet (0-299) to local face (1-100) */ + update_buddy_icon(gc->account, who, bd->face); + + g_free(who); + g_free(alias_utf8); +} + +/* process reply to get_info packet */ +void qq_process_get_buddy_info(guint8 *data, gint data_len, guint32 action, PurpleConnection *gc) +{ + qq_data *qd; + gchar **segments; + gint field_count; + gchar *icon_name; + + g_return_if_fail(data != NULL && data_len != 0); + + qd = (qq_data *) gc->proto_data; + + if (qd->client_version >= 2008) { + field_count = QQ_INFO_LAST; + } else { + field_count = QQ_INFO_LAST_2007; } + if (NULL == (segments = split_data(data, data_len, "\x1e", field_count))) + return; + +#ifdef DEBUG + info_debug(segments); +#endif + + if (action == QQ_BUDDY_INFO_SET_ICON) { + if (strtol(segments[QQ_INFO_FACE], NULL, 10) != qd->my_icon) { + icon_name = g_strdup_printf("%d", qd->my_icon); + g_free(segments[QQ_INFO_FACE]); + segments[QQ_INFO_FACE] = icon_name; + + /* Update me in buddy list */ + update_buddy_info(gc, segments); + /* send new face to server */ + request_change_info(gc, segments); + } + g_strfreev(segments); + return; + } + + update_buddy_info(gc, segments); + switch (action) { + case QQ_BUDDY_INFO_DISPLAY: + info_display_only(gc, segments); + break; + case QQ_BUDDY_INFO_SET_ICON: + g_return_if_reached(); + break; + case QQ_BUDDY_INFO_MODIFY_BASE: + info_modify_dialogue(gc, segments, QQ_FIELD_BASE); + break; + case QQ_BUDDY_INFO_MODIFY_EXT: + info_modify_dialogue(gc, segments, QQ_FIELD_EXT); + break; + case QQ_BUDDY_INFO_MODIFY_ADDR: + info_modify_dialogue(gc, segments, QQ_FIELD_ADDR); + break; + case QQ_BUDDY_INFO_MODIFY_CONTACT: + info_modify_dialogue(gc, segments, QQ_FIELD_CONTACT); + break; + default: + g_strfreev(segments); + break; + } + return; } void qq_request_get_level(PurpleConnection *gc, guint32 uid) @@ -938,101 +756,150 @@ guint8 buf[16] = {0}; gint bytes = 0; - bytes += qq_put8(buf + bytes, 0x00); + if (qd->client_version >= 2007) { + bytes += qq_put8(buf + bytes, 0x02); + } else { + bytes += qq_put8(buf + bytes, 0x00); + } bytes += qq_put32(buf + bytes, uid); + qq_send_cmd(gc, QQ_CMD_GET_LEVEL, buf, bytes); +} - qd = (qq_data *) gc->proto_data; +void qq_request_get_level_2007(PurpleConnection *gc, guint32 uid) +{ + guint8 buf[16] = {0}; + gint bytes = 0; + + bytes += qq_put8(buf + bytes, 0x08); + bytes += qq_put32(buf + bytes, uid); + bytes += qq_put8(buf + bytes, 0x00); qq_send_cmd(gc, QQ_CMD_GET_LEVEL, buf, bytes); } void qq_request_get_buddies_level(PurpleConnection *gc, gint update_class) { + qq_data *qd = (qq_data *) gc->proto_data; + PurpleBuddy *buddy; + qq_buddy_data *bd; guint8 *buf; - guint16 size; - qq_buddy *q_bud; - qq_data *qd = (qq_data *) gc->proto_data; - GList *node = qd->buddies; - gint bytes = 0; + GSList *buddies, *it; + gint bytes; - if ( qd->buddies == NULL) { - return; - } /* server only reply levels for online buddies */ - size = 4 * g_list_length(qd->buddies) + 1 + 4; - buf = g_newa(guint8, size); - bytes += qq_put8(buf + bytes, 0x00); + buf = g_newa(guint8, MAX_PACKET_SIZE); - while (NULL != node) { - q_bud = (qq_buddy *) node->data; - if (NULL != q_bud) { - bytes += qq_put32(buf + bytes, q_bud->uid); - } - node = node->next; + bytes = 0; + bytes += qq_put8(buf + bytes, 0x00); + buddies = purple_find_buddies(purple_connection_get_account(gc), NULL); + for (it = buddies; it; it = it->next) { + buddy = it->data; + if (buddy == NULL) continue; + if (buddy->proto_data == NULL) continue; + bd = (qq_buddy_data *)buddy->proto_data; + if (bd->uid == 0) continue; /* keep me as end of packet*/ + if (bd->uid == qd->uid) continue; + bytes += qq_put32(buf + bytes, bd->uid); } - - /* my id should be the end if included */ bytes += qq_put32(buf + bytes, qd->uid); - qq_send_cmd_mess(gc, QQ_CMD_GET_LEVEL, buf, size, update_class, 0); + qq_send_cmd_mess(gc, QQ_CMD_GET_LEVEL, buf, bytes, update_class, 0); } -void qq_process_get_level_reply(guint8 *decr_buf, gint decr_len, PurpleConnection *gc) +static void process_level(PurpleConnection *gc, guint8 *data, gint data_len) { + gint bytes = 0; guint32 uid, onlineTime; guint16 level, timeRemainder; - gchar *purple_name; - PurpleBuddy *b; - qq_buddy *q_bud; - gint i; - PurpleAccount *account = purple_connection_get_account(gc); - qq_data *qd = (qq_data *) gc->proto_data; - gint bytes = 0; + qq_buddy_data *bd; - decr_len--; - if (decr_len % 12 != 0) { - purple_debug_error("QQ", - "Get levels list of abnormal length. Truncating last %d bytes.\n", decr_len % 12); - decr_len -= (decr_len % 12); - } - - bytes += 1; - /* this byte seems random */ - /* - purple_debug_info("QQ", "Byte one of get_level packet: %d\n", buf[0]); - */ - for (i = 0; i < decr_len; i += 12) { - bytes += qq_get32(&uid, decr_buf + bytes); - bytes += qq_get32(&onlineTime, decr_buf + bytes); - bytes += qq_get16(&level, decr_buf + bytes); - bytes += qq_get16(&timeRemainder, decr_buf + bytes); + while (data_len - bytes >= 12) { + bytes += qq_get32(&uid, data + bytes); + bytes += qq_get32(&onlineTime, data + bytes); + bytes += qq_get16(&level, data + bytes); + bytes += qq_get16(&timeRemainder, data + bytes); purple_debug_info("QQ_LEVEL", "%d, tmOnline: %d, level: %d, tmRemainder: %d\n", uid, onlineTime, level, timeRemainder); - if (uid == qd->uid) { - qd->my_level = level; - purple_debug_warning("QQ", "Got my levels as %d\n", qd->my_level); - continue; - } - purple_name = uid_to_purple_name(uid); - if (purple_name == NULL) { - continue; - } - - b = purple_find_buddy(account, purple_name); - g_free(purple_name); - - q_bud = NULL; - if (b != NULL) { - q_bud = (qq_buddy *) b->proto_data; - } - - if (q_bud == NULL) { + bd = qq_buddy_data_find(gc, uid); + if (bd == NULL) { purple_debug_error("QQ", "Got levels of %d not in my buddy list\n", uid); continue; } - q_bud->onlineTime = onlineTime; - q_bud->level = level; - q_bud->timeRemainder = timeRemainder; + bd->onlineTime = onlineTime; + bd->level = level; + bd->timeRemainder = timeRemainder; + } + + if (bytes != data_len) { + purple_debug_error("QQ", + "Wrong format of Get levels. Truncate %d bytes.\n", data_len - bytes); } } +static void process_level_2007(PurpleConnection *gc, guint8 *data, gint data_len) +{ + gint bytes; + guint32 uid, onlineTime; + guint16 level, timeRemainder; + qq_buddy_data *bd; + guint16 str_len; + gchar *str; + gchar *str_utf8; + + bytes = 0; + bytes += qq_get32(&uid, data + bytes); + bytes += qq_get32(&onlineTime, data + bytes); + bytes += qq_get16(&level, data + bytes); + bytes += qq_get16(&timeRemainder, data + bytes); + purple_debug_info("QQ_LEVEL", "%d, tmOnline: %d, level: %d, tmRemainder: %d\n", + uid, onlineTime, level, timeRemainder); + + bd = qq_buddy_data_find(gc, uid); + if (bd == NULL) { + purple_debug_error("QQ", "Got levels of %d not in my buddy list\n", uid); + return; + } + + bd->onlineTime = onlineTime; + bd->level = level; + bd->timeRemainder = timeRemainder; + + /* extend bytes in qq2007*/ + bytes += 4; /* skip 8 bytes */ + /* qq_show_packet("Buddies level", data + bytes, data_len - bytes); */ + + do { + bytes += qq_get16(&str_len, data + bytes); + if (str_len <= 0 || bytes + str_len > data_len) { + purple_debug_error("QQ", + "Wrong format of Get levels. Truncate %d bytes.\n", data_len - bytes); + break; + } + str = g_strndup((gchar *)data + bytes, str_len); + bytes += str_len; + str_utf8 = qq_to_utf8(str, QQ_CHARSET_DEFAULT); + purple_debug_info("QQ", "%s\n", str_utf8); + g_free(str_utf8); + g_free(str); + } while (bytes < data_len); +} + +void qq_process_get_level_reply(guint8 *data, gint data_len, PurpleConnection *gc) +{ + gint bytes; + guint8 sub_cmd; + + bytes = 0; + bytes += qq_get8(&sub_cmd, data + bytes); + switch (sub_cmd) { + case 0x08: + process_level_2007(gc, data + bytes, data_len - bytes); + break; + case 0x00: + case 0x02: + default: + process_level(gc, data + bytes, data_len - bytes); + break; + } +} + diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/buddy_info.h --- a/libpurple/protocols/qq/buddy_info.h Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/buddy_info.h Wed Oct 29 21:38:53 2008 +0000 @@ -64,25 +64,28 @@ #define QQ_BUDDY_GENDER_MM 0x01 #define QQ_BUDDY_GENDER_UNKNOWN 0xff -#define QQ_ICON_PREFIX "qq_" -#define QQ_ICON_SUFFIX ".png" - enum { QQ_BUDDY_INFO_UPDATE_ONLY = 0, QQ_BUDDY_INFO_DISPLAY, - QQ_BUDDY_INFO_MODIFY, + QQ_BUDDY_INFO_SET_ICON, + QQ_BUDDY_INFO_MODIFY_BASE, + QQ_BUDDY_INFO_MODIFY_EXT, + QQ_BUDDY_INFO_MODIFY_ADDR, + QQ_BUDDY_INFO_MODIFY_CONTACT, }; -void qq_send_packet_get_info(PurpleConnection *gc, guint32 uid, gboolean show_window); +gchar *qq_get_icon_name(gint face); +gchar *qq_get_icon_path(gchar *icon_name); +void qq_change_icon_cb(PurpleConnection *gc, const char *filepath); + void qq_request_buddy_info(PurpleConnection *gc, guint32 uid, - gint update_class, guint32 ship32); -void qq_set_my_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img); -void qq_set_buddy_icon_for_user(PurpleAccount *account, const gchar *who, const gchar *icon_num, const gchar *iconfile); -void qq_prepare_modify_info(PurpleConnection *gc); -void qq_process_modify_info_reply(guint8 *data, gint data_len, PurpleConnection *gc); -void qq_process_get_buddy_info(guint8 *data, gint data_len, PurpleConnection *gc); -void qq_info_query_free(qq_data *qd); + gint update_class, int action); +void qq_set_custom_icon(PurpleConnection *gc, PurpleStoredImage *img); +void qq_process_change_info(PurpleConnection *gc, guint8 *data, gint data_len); +void qq_process_get_buddy_info(guint8 *data, gint data_len, guint32 action, PurpleConnection *gc); + void qq_request_get_level(PurpleConnection *gc, guint32 uid); +void qq_request_get_level_2007(PurpleConnection *gc, guint32 uid); void qq_request_get_buddies_level(PurpleConnection *gc, gint update_class); void qq_process_get_level_reply(guint8 *buf, gint buf_len, PurpleConnection *gc); #endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/buddy_list.c --- a/libpurple/protocols/qq/buddy_list.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/buddy_list.c Wed Oct 29 21:38:53 2008 +0000 @@ -34,10 +34,9 @@ #include "buddy_list.h" #include "buddy_opt.h" #include "char_conv.h" -#include "header_info.h" +#include "qq_define.h" #include "qq_base.h" #include "group.h" -#include "group_find.h" #include "group_internal.h" #include "group_info.h" @@ -78,16 +77,18 @@ bytes += qq_put16(raw_data + bytes, 0x0000); qq_send_cmd_mess(gc, QQ_CMD_GET_BUDDIES_ONLINE, raw_data, 5, update_class, 0); - qd->last_get_online = time(NULL); } /* position starts with 0x0000, * server may return a position tag if list is too long for one packet */ -void qq_request_get_buddies_list(PurpleConnection *gc, guint16 position, gint update_class) +void qq_request_get_buddies(PurpleConnection *gc, guint16 position, gint update_class) { + qq_data *qd; guint8 raw_data[16] = {0}; gint bytes = 0; + qd = (qq_data *) gc->proto_data; + /* 000-001 starting position, can manually specify */ bytes += qq_put16(raw_data + bytes, position); /* before Mar 18, 2004, any value can work, and we sent 00 @@ -96,6 +97,9 @@ * Now I tested that 00,00,00,00,00,01 work perfectly * March 22, found the 00,00,00 starts to work as well */ bytes += qq_put8(raw_data + bytes, 0x00); + if (qd->client_version >= 2007) { + bytes += qq_put16(raw_data + bytes, 0x0000); + } qq_send_cmd_mess(gc, QQ_CMD_GET_BUDDIES_LIST, raw_data, bytes, update_class, 0); } @@ -139,7 +143,7 @@ bytes += qq_get8(&bs->unknown2, data + bytes); /* 012-012: status */ bytes += qq_get8(&bs->status, data + bytes); - /* 013-014: client_version */ + /* 013-014: client tag */ bytes += qq_get16(&bs->unknown3, data + bytes); /* 015-030: unknown key */ bytes += qq_getdata(&(bs->unknown_key[0]), QQ_KEY_LENGTH, data + bytes); @@ -152,117 +156,118 @@ return bytes; } -#define QQ_ONLINE_BUDDY_ENTRY_LEN 38 - /* process the reply packet for get_buddies_online packet */ -guint8 qq_process_get_buddies_online_reply(guint8 *data, gint data_len, PurpleConnection *gc) +guint8 qq_process_get_buddies_online(guint8 *data, gint data_len, PurpleConnection *gc) { qq_data *qd; - gint bytes, bytes_buddy; + gint bytes, bytes_start; gint count; guint8 position; - PurpleBuddy *b; - qq_buddy *q_bud; - qq_buddy_online bo; - gchar *purple_name; + qq_buddy_data *bd; + int entry_len = 38; + + qq_buddy_status bs; + struct { + guint16 unknown1; + guint8 ext_flag; + guint8 comm_flag; + guint16 unknown2; + guint8 ending; /* 0x00 */ + } packet; g_return_val_if_fail(data != NULL && data_len != 0, -1); qd = (qq_data *) gc->proto_data; /* qq_show_packet("Get buddies online reply packet", data, len); */ + if (qd->client_version >= 2007) entry_len += 4; bytes = 0; bytes += qq_get8(&position, data + bytes); count = 0; while (bytes < data_len) { - if (data_len - bytes < QQ_ONLINE_BUDDY_ENTRY_LEN) { - purple_debug_error("QQ", "[buddies online] only %d, need %d", - (data_len - bytes), QQ_ONLINE_BUDDY_ENTRY_LEN); + if (data_len - bytes < entry_len) { + purple_debug_error("QQ", "[buddies online] only %d, need %d\n", + (data_len - bytes), entry_len); break; } - memset(&bo, 0 ,sizeof(bo)); + memset(&bs, 0 ,sizeof(bs)); + memset(&packet, 0 ,sizeof(packet)); /* set flag */ - bytes_buddy = bytes; + bytes_start = bytes; /* based on one online buddy entry */ /* 000-030 qq_buddy_status */ - bytes += get_buddy_status(&(bo.bs), data + bytes); + bytes += get_buddy_status(&bs, data + bytes); /* 031-032: */ - bytes += qq_get16(&bo.unknown1, data + bytes); + bytes += qq_get16(&packet.unknown1, data + bytes); /* 033-033: ext_flag */ - bytes += qq_get8(&bo.ext_flag, data + bytes); + bytes += qq_get8(&packet.ext_flag, data + bytes); /* 034-034: comm_flag */ - bytes += qq_get8(&bo.comm_flag, data + bytes); + bytes += qq_get8(&packet.comm_flag, data + bytes); /* 035-036: */ - bytes += qq_get16(&bo.unknown2, data + bytes); + bytes += qq_get16(&packet.unknown2, data + bytes); /* 037-037: */ - bytes += qq_get8(&bo.ending, data + bytes); /* 0x00 */ + bytes += qq_get8(&packet.ending, data + bytes); /* 0x00 */ + /* skip 4 bytes in qq2007 */ + if (qd->client_version >= 2007) bytes += 4; - if (bo.bs.uid == 0 || (bytes - bytes_buddy) != QQ_ONLINE_BUDDY_ENTRY_LEN) { + if (bs.uid == 0 || (bytes - bytes_start) != entry_len) { purple_debug_error("QQ", "uid=0 or entry complete len(%d) != %d", - (bytes - bytes_buddy), QQ_ONLINE_BUDDY_ENTRY_LEN); + (bytes - bytes_start), entry_len); continue; } /* check if it is a valid entry */ - if (bo.bs.uid == qd->uid) { - purple_debug_warning("QQ", "I am in online list %d\n", bo.bs.uid); - continue; + if (bs.uid == qd->uid) { + purple_debug_warning("QQ", "I am in online list %d\n", bs.uid); } /* update buddy information */ - purple_name = uid_to_purple_name(bo.bs.uid); - if (purple_name == NULL) { + bd = qq_buddy_data_find(gc, bs.uid); + if (bd == NULL) { purple_debug_error("QQ", - "Got an online buddy %d, but not find purple name\n", bo.bs.uid); + "Got an online buddy %d, but not in my buddy list\n", bs.uid); continue; } - b = purple_find_buddy(purple_connection_get_account(gc), purple_name); - g_free(purple_name); - - q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data; - if (q_bud == NULL) { - purple_debug_error("QQ", - "Got an online buddy %d, but not in my buddy list\n", bo.bs.uid); - continue; + /* we find one and update qq_buddy_data */ + /* + if(0 != fe->s->client_tag) + q_bud->client_tag = fe->s->client_tag; + */ + if (bd->status != bs.status || bd->comm_flag != packet.comm_flag) { + bd->status = bs.status; + bd->comm_flag = packet.comm_flag; + qq_update_buddy_status(gc, bd->uid, bd->status, bd->comm_flag); } - /* we find one and update qq_buddy */ - /* - if(0 != fe->s->client_version) - q_bud->client_version = fe->s->client_version; - */ - q_bud->ip.s_addr = bo.bs.ip.s_addr; - q_bud->port = bo.bs.port; - q_bud->status = bo.bs.status; - q_bud->ext_flag = bo.ext_flag; - q_bud->comm_flag = bo.comm_flag; - qq_update_buddy_contact(gc, q_bud); + bd->ip.s_addr = bs.ip.s_addr; + bd->port = bs.port; + bd->ext_flag = packet.ext_flag; + bd->last_update = time(NULL); count++; } if(bytes > data_len) { purple_debug_error("QQ", - "qq_process_get_buddies_online_reply: Dangerous error! maybe protocol changed, notify developers!\n"); + "qq_process_get_buddies_online: Dangerous error! maybe protocol changed, notify developers!\n"); } purple_debug_info("QQ", "Received %d online buddies, nextposition=%u\n", - count, (guint) position); + count, (guint) position); return position; } /* process reply for get_buddies_list */ -guint16 qq_process_get_buddies_list_reply(guint8 *data, gint data_len, PurpleConnection *gc) +guint16 qq_process_get_buddies(guint8 *data, gint data_len, PurpleConnection *gc) { qq_data *qd; - qq_buddy *q_bud; + qq_buddy_data bd; gint bytes_expected, count; gint bytes, buddy_bytes; + gint nickname_len; guint16 position, unknown; - guint8 pascal_len; - gchar *name; - PurpleBuddy *b; + PurpleBuddy *buddy; g_return_val_if_fail(data != NULL && data_len != 0, -1); @@ -278,60 +283,66 @@ /* the following data is buddy list in this packet */ count = 0; while (bytes < data_len) { - q_bud = g_new0(qq_buddy, 1); + memset(&bd, 0, sizeof(bd)); /* set flag */ buddy_bytes = bytes; /* 000-003: uid */ - bytes += qq_get32(&q_bud->uid, data + bytes); + bytes += qq_get32(&bd.uid, data + bytes); /* 004-005: icon index (1-255) */ - bytes += qq_get16(&q_bud->face, data + bytes); + bytes += qq_get16(&bd.face, data + bytes); /* 006-006: age */ - bytes += qq_get8(&q_bud->age, data + bytes); + bytes += qq_get8(&bd.age, data + bytes); /* 007-007: gender */ - bytes += qq_get8(&q_bud->gender, data + bytes); + bytes += qq_get8(&bd.gender, data + bytes); + + nickname_len = qq_get_vstr(&bd.nickname, QQ_CHARSET_DEFAULT, data + bytes); + bytes += nickname_len; + qq_filter_str(bd.nickname); - pascal_len = convert_as_pascal_string(data + bytes, &q_bud->nickname, QQ_CHARSET_DEFAULT); - bytes += pascal_len; - qq_filter_str(q_bud->nickname); - + /* Fixme: merge following as 32bit flag */ bytes += qq_get16(&unknown, data + bytes); - bytes += qq_get8(&q_bud->ext_flag, data + bytes); - bytes += qq_get8(&q_bud->comm_flag, data + bytes); + bytes += qq_get8(&bd.ext_flag, data + bytes); + bytes += qq_get8(&bd.comm_flag, data + bytes); - bytes_expected = 12 + pascal_len; + if (qd->client_version >= 2007) { + bytes += 4; /* skip 4 bytes */ + bytes_expected = 16 + nickname_len; + } else { + bytes_expected = 12 + nickname_len; + } - if (q_bud->uid == 0 || (bytes - buddy_bytes) != bytes_expected) { + if (bd.uid == 0 || (bytes - buddy_bytes) != bytes_expected) { purple_debug_info("QQ", "Buddy entry, expect %d bytes, read %d bytes\n", bytes_expected, bytes - buddy_bytes); - g_free(q_bud->nickname); - g_free(q_bud); + g_free(bd.nickname); continue; } else { count++; } #if 1 - purple_debug_info("QQ", - "buddy [%09d]: ext_flag=0x%02x, comm_flag=0x%02x, nick=%s\n", - q_bud->uid, q_bud->ext_flag, q_bud->comm_flag, q_bud->nickname); + purple_debug_info("QQ", "buddy [%09d]: ext_flag=0x%02x, comm_flag=0x%02x, nick=%s\n", + bd.uid, bd.ext_flag, bd.comm_flag, bd.nickname); #endif - name = uid_to_purple_name(q_bud->uid); - b = purple_find_buddy(gc->account, name); - g_free(name); + buddy = qq_buddy_find_or_new(gc, bd.uid); + if (buddy == NULL || buddy->proto_data == NULL) { + g_free(bd.nickname); + continue; + } + purple_blist_server_alias_buddy(buddy, bd.nickname); + bd.last_update = time(NULL); + qq_update_buddy_status(gc, bd.uid, bd.status, bd.comm_flag); - if (b == NULL) { - b = qq_add_buddy_by_recv_packet(gc, q_bud->uid, TRUE, FALSE); - } - - b->proto_data = q_bud; - qd->buddies = g_list_append(qd->buddies, q_bud); - qq_update_buddy_contact(gc, q_bud); + g_memmove(buddy->proto_data, &bd, sizeof(qq_buddy_data)); + /* nickname has been copy to buddy_data do not free + g_free(bd.nickname); + */ } if(bytes > data_len) { purple_debug_error("QQ", - "qq_process_get_buddies_list_reply: Dangerous error! maybe protocol changed, notify developers!"); + "qq_process_get_buddies: Dangerous error! maybe protocol changed, notify developers!"); } purple_debug_info("QQ", "Received %d buddies, nextposition=%u\n", @@ -347,8 +358,8 @@ guint8 sub_cmd, reply_code; guint32 unknown, position; guint32 uid; - guint8 type, groupid; - qq_group *group; + guint8 type; + qq_room_data *rmd; g_return_val_if_fail(data != NULL && data_len != 0, -1); @@ -360,7 +371,7 @@ bytes += qq_get8(&reply_code, data + bytes); if(0 != reply_code) { - purple_debug_warning("QQ", "qq_process_get_buddies_and_rooms, %d", reply_code); + purple_debug_warning("QQ", "qq_process_get_buddies_and_rooms, %d\n", reply_code); } bytes += qq_get32(&unknown, data + bytes); @@ -373,29 +384,23 @@ bytes += qq_get32(&uid, data + bytes); /* 04: type 0x1:buddy 0x4:Qun */ bytes += qq_get8(&type, data + bytes); - /* 05: groupid*4 */ /* seems to always be 0 */ - bytes += qq_get8(&groupid, data + bytes); - /* - purple_debug_info("QQ", "groupid: %i\n", groupid); - groupid >>= 2; - */ + /* 05: skip unknow 0x00 */ + bytes += 1; if (uid == 0 || (type != 0x1 && type != 0x4)) { purple_debug_info("QQ", "Buddy entry, uid=%d, type=%d", uid, type); continue; } if(0x1 == type) { /* a buddy */ /* don't do anything but count - buddies are handled by - * qq_request_get_buddies_list */ + * qq_request_get_buddies */ ++i; } else { /* a group */ - group = qq_room_search_id(gc, uid); - if(group == NULL) { - purple_debug_info("QQ", - "Not find room id %d in qq_process_get_buddies_and_rooms\n", uid); - qq_set_pending_id(&qd->adding_groups_from_server, uid, TRUE); + rmd = qq_room_data_find(gc, uid); + if(rmd == NULL) { + purple_debug_info("QQ", "Unknow room id %d", uid); + qq_send_room_cmd_only(gc, QQ_ROOM_CMD_GET_INFO, uid); } else { - group->my_role = QQ_ROOM_ROLE_YES; - qq_group_refresh(gc, group); + rmd->my_role = QQ_ROOM_ROLE_YES; } ++j; } @@ -416,39 +421,34 @@ /* TODO: figure out what's going on with the IP region. Sometimes I get valid IP addresses, * but the port number's weird, other times I get 0s. I get these simultaneously on the same buddy, * using different accounts to get info. */ - -/* check if status means online or offline */ -gboolean is_online(guint8 status) +static guint8 get_status_from_purple(PurpleConnection *gc) { - switch(status) { - case QQ_BUDDY_ONLINE_NORMAL: - case QQ_BUDDY_ONLINE_AWAY: - case QQ_BUDDY_ONLINE_INVISIBLE: - return TRUE; - case QQ_BUDDY_CHANGE_TO_OFFLINE: - return FALSE; - } - return FALSE; -} - -/* Help calculate the correct icon index to tell the server. */ -gint get_icon_offset(PurpleConnection *gc) -{ + qq_data *qd; PurpleAccount *account; PurplePresence *presence; + guint8 ret; + qd = (qq_data *) gc->proto_data; account = purple_connection_get_account(gc); presence = purple_account_get_presence(account); if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_INVISIBLE)) { - return 2; + ret = QQ_BUDDY_ONLINE_INVISIBLE; + } else if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_UNAVAILABLE)) + { + if (qd->client_version >= 2007) { + ret = QQ_BUDDY_ONLINE_BUSY; + } else { + ret = QQ_BUDDY_ONLINE_INVISIBLE; + } } else if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_AWAY) || purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_EXTENDED_AWAY) || purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_UNAVAILABLE)) { - return 1; + ret = QQ_BUDDY_ONLINE_AWAY; } else { - return 0; + ret = QQ_BUDDY_ONLINE_NORMAL; } + return ret; } /* send a packet to change my online status */ @@ -470,37 +470,37 @@ if (!qd->is_login) return; - if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_INVISIBLE)) { - away_cmd = QQ_BUDDY_ONLINE_INVISIBLE; - } else if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_AWAY) - || purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_EXTENDED_AWAY) - || purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_UNAVAILABLE)) { - away_cmd = QQ_BUDDY_ONLINE_AWAY; - } else { - away_cmd = QQ_BUDDY_ONLINE_NORMAL; - } + away_cmd = get_status_from_purple(gc); misc_status = 0x00000000; fake_video = purple_prefs_get_bool("/plugins/prpl/qq/show_fake_video"); if (fake_video) misc_status |= QQ_MISC_STATUS_HAVING_VIIDEO; - bytes = 0; - bytes += qq_put8(raw_data + bytes, away_cmd); - bytes += qq_put32(raw_data + bytes, misc_status); - + if (qd->client_version >= 2007) { + bytes = 0; + bytes += qq_put8(raw_data + bytes, away_cmd); + /* status version */ + bytes += qq_put16(raw_data + bytes, 0); + bytes += qq_put16(raw_data + bytes, 0); + bytes += qq_put32(raw_data + bytes, misc_status); + /* Fixme: custom status message, now is empty */ + bytes += qq_put16(raw_data + bytes, 0); + } else { + bytes = 0; + bytes += qq_put8(raw_data + bytes, away_cmd); + bytes += qq_put32(raw_data + bytes, misc_status); + } qq_send_cmd_mess(gc, QQ_CMD_CHANGE_STATUS, raw_data, bytes, update_class, 0); } /* parse the reply packet for change_status */ -void qq_process_change_status_reply(guint8 *data, gint data_len, PurpleConnection *gc) +void qq_process_change_status(guint8 *data, gint data_len, PurpleConnection *gc) { qq_data *qd; gint bytes; guint8 reply; - PurpleBuddy *b; - qq_buddy *q_bud; - gchar *name; + qq_buddy_data *bd; g_return_if_fail(data != NULL && data_len != 0); @@ -514,12 +514,11 @@ } /* purple_debug_info("QQ", "Change status OK\n"); */ - name = uid_to_purple_name(qd->uid); - b = purple_find_buddy(gc->account, name); - g_free(name); - q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data; - if (q_bud != NULL) { - qq_update_buddy_contact(gc, q_bud); + bd = qq_buddy_data_find(gc, qd->uid); + if (bd != NULL) { + bd->status = get_status_from_purple(gc); + bd->last_update = time(NULL); + qq_update_buddy_status(gc, bd->uid, bd->status, bd->comm_flag); } } @@ -529,10 +528,8 @@ qq_data *qd; gint bytes; guint32 my_uid; - PurpleBuddy *b; - qq_buddy *q_bud; + qq_buddy_data *bd; qq_buddy_status bs; - gchar *name; g_return_if_fail(data != NULL && data_len != 0); @@ -552,57 +549,46 @@ * QQ_BUDDY_ONLINE_INVISIBLE */ bytes += qq_get32(&my_uid, data + bytes); - name = uid_to_purple_name(bs.uid); - b = purple_find_buddy(gc->account, name); - g_free(name); - q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data; - if (q_bud == NULL) { + bd = qq_buddy_data_find(gc, bs.uid); + if (bd == NULL) { purple_debug_warning("QQ", "Get status of unknown buddy %d\n", bs.uid); return; } if(bs.ip.s_addr != 0) { - q_bud->ip.s_addr = bs.ip.s_addr; - q_bud->port = bs.port; + bd->ip.s_addr = bs.ip.s_addr; + bd->port = bs.port; } - q_bud->status =bs.status; + if (bd->status != bs.status) { + bd->status = bs.status; + qq_update_buddy_status(gc, bd->uid, bd->status, bd->comm_flag); + } + bd->last_update = time(NULL); - if (q_bud->status == QQ_BUDDY_ONLINE_NORMAL && q_bud->level <= 0) { - qq_request_get_level(gc, q_bud->uid); + if (bd->status == QQ_BUDDY_ONLINE_NORMAL && bd->level <= 0) { + if (qd->client_version >= 2007) { + qq_request_get_level_2007(gc, bd->uid); + } else { + qq_request_get_level(gc, bd->uid); + } } - qq_update_buddy_contact(gc, q_bud); } /*TODO: maybe this should be qq_update_buddy_status() ?*/ -void qq_update_buddy_contact(PurpleConnection *gc, qq_buddy *q_bud) +void qq_update_buddy_status(PurpleConnection *gc, guint32 uid, guint8 status, guint8 flag) { - gchar *purple_name; - PurpleBuddy *bud; + gchar *who; gchar *status_id; - g_return_if_fail(q_bud != NULL); - - purple_name = uid_to_purple_name(q_bud->uid); - if (purple_name == NULL) { - purple_debug_error("QQ", "Not find purple name: %d\n", q_bud->uid); - return; - } + g_return_if_fail(uid != 0); - bud = purple_find_buddy(gc->account, purple_name); - if (bud == NULL) { - purple_debug_error("QQ", "Not find buddy: %d\n", q_bud->uid); - g_free(purple_name); - return; - } - - purple_blist_server_alias_buddy(bud, q_bud->nickname); /* server */ - q_bud->last_update = time(NULL); + who = uid_to_purple_name(uid); /* purple supports signon and idle time * but it is not much use for QQ, I do not use them */ /* serv_got_update(gc, name, online, 0, q_bud->signon, q_bud->idle, bud->uc); */ status_id = "available"; - switch(q_bud->status) { + switch(status) { case QQ_BUDDY_OFFLINE: status_id = "offline"; break; @@ -618,42 +604,81 @@ case QQ_BUDDY_ONLINE_INVISIBLE: status_id = "invisible"; break; + case QQ_BUDDY_ONLINE_BUSY: + status_id = "busy"; + break; default: status_id = "invisible"; - purple_debug_error("QQ", "unknown status: %x\n", q_bud->status); + purple_debug_error("QQ", "unknown status: 0x%X\n", status); break; } - purple_debug_info("QQ", "buddy %d %s\n", q_bud->uid, status_id); - purple_prpl_got_user_status(gc->account, purple_name, status_id, NULL); + purple_debug_info("QQ", "Update buddy %s status as %s\n", who, status_id); + purple_prpl_got_user_status(gc->account, who, status_id, NULL); - if (q_bud->comm_flag & QQ_COMM_FLAG_MOBILE && q_bud->status != QQ_BUDDY_OFFLINE) - purple_prpl_got_user_status(gc->account, purple_name, "mobile", NULL); + if (flag & QQ_COMM_FLAG_MOBILE && status != QQ_BUDDY_OFFLINE) + purple_prpl_got_user_status(gc->account, who, "mobile", NULL); else - purple_prpl_got_user_status_deactive(gc->account, purple_name, "mobile"); + purple_prpl_got_user_status_deactive(gc->account, who, "mobile"); - g_free(purple_name); + g_free(who); } /* refresh all buddies online/offline, * after receiving reply for get_buddies_online packet */ -void qq_refresh_all_buddy_status(PurpleConnection *gc) +void qq_update_buddyies_status(PurpleConnection *gc) { - time_t now; - GList *list; qq_data *qd; - qq_buddy *q_bud; + PurpleBuddy *buddy; + qq_buddy_data *bd; + GSList *buddies, *it; + time_t tm_limit = time(NULL); qd = (qq_data *) (gc->proto_data); - now = time(NULL); - list = qd->buddies; + + tm_limit -= QQ_UPDATE_ONLINE_INTERVAL; + + buddies = purple_find_buddies(purple_connection_get_account(gc), NULL); + for (it = buddies; it; it = it->next) { + buddy = it->data; + if (buddy == NULL) continue; + if (buddy->proto_data == NULL) continue; - while (list != NULL) { - q_bud = (qq_buddy *) list->data; - if (q_bud != NULL && now > q_bud->last_update + QQ_UPDATE_ONLINE_INTERVAL - && q_bud->status != QQ_BUDDY_ONLINE_INVISIBLE) { - q_bud->status = QQ_BUDDY_CHANGE_TO_OFFLINE; - qq_update_buddy_contact(gc, q_bud); - } - list = list->next; + bd = (qq_buddy_data *)buddy->proto_data; + if (bd->uid == 0) continue; + if (bd->uid == qd->uid) continue; /* my status is always online in my buddy list */ + if (tm_limit < bd->last_update) continue; + if (bd->status == QQ_BUDDY_ONLINE_INVISIBLE) continue; + if (bd->status == QQ_BUDDY_CHANGE_TO_OFFLINE) continue; + + bd->status = QQ_BUDDY_CHANGE_TO_OFFLINE; + bd->last_update = time(NULL); + qq_update_buddy_status(gc, bd->uid, bd->status, bd->comm_flag); } } + +void qq_buddy_data_free_all(PurpleConnection *gc) +{ + qq_data *qd; + PurpleBuddy *buddy; + GSList *buddies, *it; + gint count = 0; + + qd = (qq_data *) (gc->proto_data); + + buddies = purple_find_buddies(purple_connection_get_account(gc), NULL); + for (it = buddies; it; it = it->next) { + buddy = it->data; + if (buddy == NULL) continue; + if (buddy->proto_data == NULL) continue; + + qq_buddy_data_free(buddy->proto_data); + buddy->proto_data = NULL; + + count++; + } + + if (count > 0) { + purple_debug_info("QQ", "%d buddies' data are freed\n", count); + } +} + diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/buddy_list.h --- a/libpurple/protocols/qq/buddy_list.h Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/buddy_list.h Wed Oct 29 21:38:53 2008 +0000 @@ -40,33 +40,20 @@ guint8 unknown_key[QQ_KEY_LENGTH]; } qq_buddy_status; -enum { - QQ_BUDDY_OFFLINE = 0x00, - QQ_BUDDY_ONLINE_NORMAL = 10, - QQ_BUDDY_CHANGE_TO_OFFLINE = 20, - QQ_BUDDY_ONLINE_AWAY = 30, - QQ_BUDDY_ONLINE_INVISIBLE = 40 -}; +void qq_request_get_buddies_online(PurpleConnection *gc, guint8 position, gint update_class); +guint8 qq_process_get_buddies_online(guint8 *data, gint data_len, PurpleConnection *gc); -void qq_request_get_buddies_online(PurpleConnection *gc, guint8 position, gint update_class); -guint8 qq_process_get_buddies_online_reply(guint8 *data, gint data_len, PurpleConnection *gc); - -void qq_request_get_buddies_list(PurpleConnection *gc, guint16 position, gint update_class); -guint16 qq_process_get_buddies_list_reply(guint8 *data, gint data_len, PurpleConnection *gc); +void qq_request_get_buddies(PurpleConnection *gc, guint16 position, gint update_class); +guint16 qq_process_get_buddies(guint8 *data, gint data_len, PurpleConnection *gc); void qq_request_get_buddies_and_rooms(PurpleConnection *gc, guint32 position, gint update_class); guint32 qq_process_get_buddies_and_rooms(guint8 *data, gint data_len, PurpleConnection *gc); -void qq_refresh_all_buddy_status(PurpleConnection *gc); - -gboolean is_online(guint8 status); - -gint get_icon_offset(PurpleConnection *gc); - void qq_request_change_status(PurpleConnection *gc, gint update_class); -void qq_process_change_status_reply(guint8 *data, gint data_len, PurpleConnection *gc); +void qq_process_change_status(guint8 *data, gint data_len, PurpleConnection *gc); void qq_process_buddy_change_status(guint8 *data, gint data_len, PurpleConnection *gc); -void qq_refresh_all_buddy_status(PurpleConnection *gc); -void qq_update_buddy_contact(PurpleConnection *gc, qq_buddy *q_bud); +void qq_update_buddyies_status(PurpleConnection *gc); +void qq_update_buddy_status(PurpleConnection *gc, guint32 uid, guint8 status, guint8 flag); +void qq_buddy_data_free_all(PurpleConnection *gc); #endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/buddy_opt.c --- a/libpurple/protocols/qq/buddy_opt.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/buddy_opt.c Wed Oct 29 21:38:53 2008 +0000 @@ -26,12 +26,13 @@ #include "internal.h" #include "notify.h" #include "request.h" +#include "privacy.h" #include "buddy_info.h" #include "buddy_list.h" #include "buddy_opt.h" #include "char_conv.h" -#include "header_info.h" +#include "qq_define.h" #include "im.h" #include "qq_base.h" #include "packet_parse.h" @@ -39,12 +40,8 @@ #include "utils.h" #define PURPLE_GROUP_QQ_FORMAT "QQ (%s)" -#define PURPLE_GROUP_QQ_UNKNOWN "QQ Unknown" -#define PURPLE_GROUP_QQ_BLOCKED "QQ Blocked" -#define QQ_REMOVE_BUDDY_REPLY_OK 0x00 #define QQ_REMOVE_SELF_REPLY_OK 0x00 -#define QQ_ADD_BUDDY_AUTH_REPLY_OK 0x30 /* ASCII value of "0" */ enum { QQ_MY_AUTH_APPROVE = 0x30, /* ASCII value of "0" */ @@ -52,24 +49,419 @@ QQ_MY_AUTH_REQUEST = 0x32, /* ASCII value of "2" */ }; -typedef struct _qq_add_buddy_request { +typedef struct _qq_buddy_req { + PurpleConnection *gc; guint32 uid; - guint16 seq; -} qq_add_buddy_request; + guint8 *auth; + guint8 auth_len; +} qq_buddy_req; + +void add_buddy_authorize_input(PurpleConnection *gc, guint32 uid, + guint8 *auth, guint8 auth_len); + +static void buddy_req_free(qq_buddy_req *add_req) +{ + g_return_if_fail(add_req != NULL); + if (add_req->auth) g_free(add_req->auth); + g_free(add_req); +} + +static void buddy_req_cancel_cb(qq_buddy_req *add_req, const gchar *msg) +{ + g_return_if_fail(add_req != NULL); + buddy_req_free(add_req); +} + +PurpleGroup *qq_group_find_or_new(const gchar *group_name) +{ + PurpleGroup *g; + + g_return_val_if_fail(group_name != NULL, NULL); + + g = purple_find_group(group_name); + if (g == NULL) { + g = purple_group_new(group_name); + purple_blist_add_group(g, NULL); + purple_debug_warning("QQ", "Add new group: %s\n", group_name); + } + + return g; +} + +static qq_buddy_data *qq_buddy_data_new(guint32 uid) +{ + qq_buddy_data *bd = g_new0(qq_buddy_data, 1); + memset(bd, 0, sizeof(qq_buddy_data)); + bd->uid = uid; + bd->status = QQ_BUDDY_OFFLINE; + return bd; +} + +qq_buddy_data *qq_buddy_data_find(PurpleConnection *gc, guint32 uid) +{ + gchar *who; + PurpleBuddy *buddy; + + g_return_val_if_fail(gc != NULL, NULL); + + who = uid_to_purple_name(uid); + if (who == NULL) return NULL; + buddy = purple_find_buddy(purple_connection_get_account(gc), who); + g_free(who); + + if (buddy == NULL) { + purple_debug_error("QQ", "Can not find purple buddy of %d\n", uid); + return NULL; + } + if (buddy->proto_data == NULL) { + purple_debug_error("QQ", "Can not find buddy data of %d\n", uid); + return NULL; + } + return (qq_buddy_data *)buddy->proto_data; +} + +void qq_buddy_data_free(qq_buddy_data *bd) +{ + g_return_if_fail(bd != NULL); + + if (bd->nickname) g_free(bd->nickname); + g_free(bd); +} + +/* create purple buddy without data and display with no-auth icon */ +PurpleBuddy *qq_buddy_new(PurpleConnection *gc, guint32 uid) +{ + PurpleBuddy *buddy; + PurpleGroup *group; + gchar *who; + gchar *group_name; + + g_return_val_if_fail(gc->account != NULL && uid != 0, NULL); + + group_name = g_strdup_printf(PURPLE_GROUP_QQ_FORMAT, + purple_account_get_username(gc->account)); + group = qq_group_find_or_new(group_name); + if (group == NULL) { + purple_debug_error("QQ", "Failed creating group %s\n", group_name); + return NULL; + } + + who = uid_to_purple_name(uid); + + purple_debug_info("QQ", "Add new purple buddy: [%s]\n", who); + buddy = purple_buddy_new(gc->account, who, NULL); /* alias is NULL */ + buddy->proto_data = NULL; + + g_free(who); + + purple_blist_add_buddy(buddy, NULL, group, NULL); + + g_free(group_name); + + return buddy; +} + +static void qq_buddy_free(PurpleBuddy *buddy) +{ + g_return_if_fail(buddy); + if (buddy->proto_data) { + qq_buddy_data_free(buddy->proto_data); + } + buddy->proto_data = NULL; + purple_blist_remove_buddy(buddy); +} + +PurpleBuddy *qq_buddy_find(PurpleConnection *gc, guint32 uid) +{ + PurpleBuddy *buddy; + gchar *who; + + g_return_val_if_fail(gc->account != NULL && uid != 0, NULL); + + who = uid_to_purple_name(uid); + buddy = purple_find_buddy(gc->account, who); + g_free(who); + return buddy; +} + +PurpleBuddy *qq_buddy_find_or_new(PurpleConnection *gc, guint32 uid) +{ + PurpleBuddy *buddy; + + g_return_val_if_fail(gc->account != NULL && uid != 0, NULL); + + buddy = qq_buddy_find(gc, uid); + if (buddy == NULL) { + buddy = qq_buddy_new(gc, uid); + if (buddy == NULL) { + return NULL; + } + } + + if (buddy->proto_data != NULL) { + return buddy; + } + + buddy->proto_data = qq_buddy_data_new(uid); + return buddy; +} /* send packet to remove a buddy from my buddy list */ -static void _qq_send_packet_remove_buddy(PurpleConnection *gc, guint32 uid) +static void request_remove_buddy(PurpleConnection *gc, guint32 uid) { gchar uid_str[11]; + gint bytes; g_return_if_fail(uid > 0); g_snprintf(uid_str, sizeof(uid_str), "%d", uid); - qq_send_cmd(gc, QQ_CMD_DEL_BUDDY, (guint8 *) uid_str, strlen(uid_str)); + bytes = strlen(uid_str); + qq_send_cmd_mess(gc, QQ_CMD_REMOVE_BUDDY, (guint8 *) uid_str, bytes, 0, uid); +} + +static void request_remove_buddy_ex(PurpleConnection *gc, + guint32 uid, guint8 *auth, guint8 auth_len) +{ + gint bytes; + guint8 *raw_data; + gchar uid_str[16]; + + g_return_if_fail(uid != 0); + g_return_if_fail(auth != NULL && auth_len > 0); + + raw_data = g_newa(guint8, auth_len + sizeof(uid_str) ); + bytes = 0; + bytes += qq_put8(raw_data + bytes, auth_len); + bytes += qq_putdata(raw_data + bytes, auth, auth_len); + + g_snprintf(uid_str, sizeof(uid_str), "%d", uid); + bytes += qq_putdata(raw_data + bytes, (guint8 *)uid_str, strlen(uid_str)); + + qq_send_cmd_mess(gc, QQ_CMD_REMOVE_BUDDY, raw_data, bytes, 0, uid); +} + +void qq_request_auth_code(PurpleConnection *gc, guint8 cmd, guint16 sub_cmd, guint32 uid) +{ + guint8 raw_data[16]; + gint bytes; + + g_return_if_fail(uid > 0); + bytes = 0; + bytes += qq_put8(raw_data + bytes, cmd); + bytes += qq_put16(raw_data + bytes, sub_cmd); + bytes += qq_put32(raw_data + bytes, uid); + + qq_send_cmd_mess(gc, QQ_CMD_AUTH_CODE, raw_data, bytes, 0, uid); +} + +void qq_process_auth_code(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid) +{ + qq_data *qd; + gint bytes; + guint8 cmd, reply; + guint16 sub_cmd; + guint8 *code = NULL; + guint16 code_len = 0; + + g_return_if_fail(data != NULL && data_len != 0); + g_return_if_fail(uid != 0); + + qd = (qq_data *) gc->proto_data; + + qq_show_packet("qq_process_auth_code", data, data_len); + bytes = 0; + bytes += qq_get8(&cmd, data + bytes); + bytes += qq_get16(&sub_cmd, data + bytes); + bytes += qq_get8(&reply, data + bytes); + g_return_if_fail(bytes + 2 <= data_len); + + bytes += qq_get16(&code_len, data + bytes); + g_return_if_fail(code_len > 0); + g_return_if_fail(bytes + code_len <= data_len); + code = g_newa(guint8, code_len); + bytes += qq_getdata(code, code_len, data + bytes); + + if (cmd == QQ_AUTH_INFO_BUDDY && sub_cmd == QQ_AUTH_INFO_REMOVE_BUDDY) { + request_remove_buddy_ex(gc, uid, code, code_len); + return; + } + if (cmd == QQ_AUTH_INFO_BUDDY && sub_cmd == QQ_AUTH_INFO_ADD_BUDDY) { + add_buddy_authorize_input(gc, uid, code, code_len); + return; + } + purple_debug_info("QQ", "Got auth info cmd 0x%x, sub 0x%x, reply 0x%x\n", + cmd, sub_cmd, reply); +} + +static void add_buddy_question_cb(qq_buddy_req *add_req, const gchar *text) +{ + g_return_if_fail(add_req != NULL); + if (add_req->gc == NULL || add_req->uid == 0) { + buddy_req_free(add_req); + return; + } + + qq_request_question(add_req->gc, QQ_QUESTION_ANSWER, add_req->uid, NULL, text); + buddy_req_free(add_req); +} + +static void add_buddy_question_input(PurpleConnection *gc, guint32 uid, gchar *question) +{ + gchar *who, *msg; + qq_buddy_req *add_req; + g_return_if_fail(uid != 0); + + add_req = g_new0(qq_buddy_req, 1); + add_req->gc = gc; + add_req->uid = uid; + add_req->auth = NULL; + add_req->auth_len = 0; + + who = uid_to_purple_name(uid); + msg = g_strdup_printf(_("%d needs Q&A"), uid); + purple_request_input(gc, _("Add buddy Q&A"), msg, + _("Input answer here"), + NULL, + TRUE, FALSE, NULL, + _("Send"), G_CALLBACK(add_buddy_question_cb), + _("Cancel"), G_CALLBACK(buddy_req_cancel_cb), + purple_connection_get_account(gc), who, NULL, + add_req); + + g_free(msg); + g_free(who); +} + +void qq_request_question(PurpleConnection *gc, + guint8 cmd, guint32 uid, const gchar *question_utf8, const gchar *answer_utf8) +{ + guint8 raw_data[MAX_PACKET_SIZE - 16]; + gint bytes; + + g_return_if_fail(uid > 0); + bytes = 0; + bytes += qq_put8(raw_data + bytes, cmd); + if (cmd == QQ_QUESTION_GET) { + bytes += qq_put8(raw_data + bytes, 0); + qq_send_cmd_mess(gc, QQ_CMD_BUDDY_QUESTION, raw_data, bytes, 0, uid); + return; + } + if (cmd == QQ_QUESTION_SET) { + bytes += qq_put_vstr(raw_data + bytes, question_utf8, QQ_CHARSET_DEFAULT); + bytes += qq_put_vstr(raw_data + bytes, answer_utf8, QQ_CHARSET_DEFAULT); + bytes += qq_put8(raw_data + bytes, 0); + qq_send_cmd_mess(gc, QQ_CMD_BUDDY_QUESTION, raw_data, bytes, 0, uid); + return; + } + /* Unknow 2 bytes, 0x(00 01) */ + bytes += qq_put8(raw_data + bytes, 0x00); + bytes += qq_put8(raw_data + bytes, 0x01); + g_return_if_fail(uid != 0); + bytes += qq_put32(raw_data + bytes, uid); + if (cmd == QQ_QUESTION_REQUEST) { + qq_send_cmd_mess(gc, QQ_CMD_BUDDY_QUESTION, raw_data, bytes, 0, uid); + return; + } + bytes += qq_put_vstr(raw_data + bytes, answer_utf8, QQ_CHARSET_DEFAULT); + bytes += qq_put8(raw_data + bytes, 0); + qq_send_cmd_mess(gc, QQ_CMD_BUDDY_QUESTION, raw_data, bytes, 0, uid); + return; +} + +static void request_add_buddy_by_question(PurpleConnection *gc, guint32 uid, + guint8 *code, guint16 code_len) +{ + guint8 raw_data[MAX_PACKET_SIZE - 16]; + gint bytes = 0; + + g_return_if_fail(uid != 0 && code_len > 0); + + bytes = 0; + bytes += qq_put8(raw_data + bytes, 0x10); + bytes += qq_put32(raw_data + bytes, uid); + bytes += qq_put16(raw_data + bytes, 0); + + bytes += qq_put8(raw_data + bytes, 0); + bytes += qq_put8(raw_data + bytes, 0); /* no auth code */ + + bytes += qq_put16(raw_data + bytes, code_len); + bytes += qq_putdata(raw_data + bytes, code, code_len); + + bytes += qq_put8(raw_data + bytes, 1); /* ALLOW ADD ME FLAG */ + bytes += qq_put8(raw_data + bytes, 0); /* group number? */ + qq_send_cmd(gc, QQ_CMD_ADD_BUDDY_AUTH_EX, raw_data, bytes); +} + +void qq_process_question(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid) +{ + qq_data *qd; + gint bytes; + guint8 cmd, reply; + gchar *question, *answer; + guint16 code_len; + guint8 *code; + + g_return_if_fail(data != NULL && data_len != 0); + + qd = (qq_data *) gc->proto_data; + + qq_show_packet("qq_process_question", data, data_len); + bytes = 0; + bytes += qq_get8(&cmd, data + bytes); + if (cmd == QQ_QUESTION_GET) { + bytes += qq_get_vstr(&question, QQ_CHARSET_DEFAULT, data + bytes); + bytes += qq_get_vstr(&answer, QQ_CHARSET_DEFAULT, data + bytes); + purple_debug_info("QQ", "Get buddy adding Q&A:\n%s\n%s\n", question, answer); + g_free(question); + g_free(answer); + return; + } + if (cmd == QQ_QUESTION_SET) { + bytes += qq_get8(&reply, data + bytes); + if (reply == 0) { + purple_debug_info("QQ", "Successed setting Q&A\n"); + } else { + purple_debug_warning("QQ", "Failed setting Q&A, reply %d\n", reply); + } + return; + } + + g_return_if_fail(uid != 0); + bytes += 2; /* skip 2 bytes, 0x(00 01)*/ + if (cmd == QQ_QUESTION_REQUEST) { + bytes += qq_get8(&reply, data + bytes); + if (reply == 0x01) { + purple_debug_warning("QQ", "Failed getting question, reply %d\n", reply); + return; + } + bytes += qq_get_vstr(&question, QQ_CHARSET_DEFAULT, data + bytes); + purple_debug_info("QQ", "Get buddy question:\n%s\n", question); + add_buddy_question_input(gc, uid, question); + g_free(question); + return; + } + + if (cmd == QQ_QUESTION_ANSWER) { + bytes += qq_get8(&reply, data + bytes); + if (reply == 0x01) { + purple_notify_error(gc, _("Add Buddy"), _("Invalid answer."), NULL); + return; + } + bytes += qq_get16(&code_len, data + bytes); + g_return_if_fail(code_len > 0); + g_return_if_fail(bytes + code_len <= data_len); + + code = g_newa(guint8, code_len); + bytes += qq_getdata(code, code_len, data + bytes); + request_add_buddy_by_question(gc, uid, code, code_len); + return; + } + + g_return_if_reached(); } /* try to remove myself from someone's buddy list */ -static void _qq_send_packet_remove_self_from(PurpleConnection *gc, guint32 uid) +static void request_buddy_remove_me(PurpleConnection *gc, guint32 uid) { guint8 raw_data[16] = {0}; gint bytes = 0; @@ -78,31 +470,36 @@ bytes += qq_put32(raw_data + bytes, uid); - qq_send_cmd(gc, QQ_CMD_REMOVE_SELF, raw_data, bytes); + qq_send_cmd_mess(gc, QQ_CMD_REMOVE_ME, raw_data, bytes, 0, uid); } /* try to add a buddy without authentication */ -static void _qq_send_packet_add_buddy(PurpleConnection *gc, guint32 uid) +static void request_add_buddy_no_auth(PurpleConnection *gc, guint32 uid) { - qq_data *qd = (qq_data *) gc->proto_data; - qq_add_buddy_request *req; gchar uid_str[11]; g_return_if_fail(uid > 0); /* we need to send the ascii code of this uid to qq server */ g_snprintf(uid_str, sizeof(uid_str), "%d", uid); - qq_send_cmd(gc, QQ_CMD_ADD_BUDDY_WO_AUTH, (guint8 *) uid_str, strlen(uid_str)); + qq_send_cmd_mess(gc, QQ_CMD_ADD_BUDDY_NO_AUTH, + (guint8 *) uid_str, strlen(uid_str), 0, uid); +} - /* must be set after sending packet to get the correct send_seq */ - req = g_new0(qq_add_buddy_request, 1); - req->seq = qd->send_seq; - req->uid = uid; - qd->add_buddy_request = g_list_append(qd->add_buddy_request, req); +static void request_add_buddy_no_auth_ex(PurpleConnection *gc, guint32 uid) +{ + guint bytes; + guint8 raw_data[16]; + + g_return_if_fail(uid != 0); + + bytes = 0; + bytes += qq_put32(raw_data + bytes, uid); + qq_send_cmd_mess(gc, QQ_CMD_ADD_BUDDY_NO_AUTH_EX, raw_data, bytes, 0, uid); } /* this buddy needs authentication, text conversion is done at lowest level */ -static void _qq_send_packet_buddy_auth(PurpleConnection *gc, guint32 uid, const gchar response, const gchar *text) +static void request_add_buddy_auth(PurpleConnection *gc, guint32 uid, const gchar response, const gchar *text) { gchar *text_qq, uid_str[11]; guint8 bar, *raw_data; @@ -125,130 +522,196 @@ g_free(text_qq); } - qq_send_cmd(gc, QQ_CMD_BUDDY_AUTH, raw_data, bytes); + qq_send_cmd(gc, QQ_CMD_ADD_BUDDY_AUTH, raw_data, bytes); } -static void _qq_send_packet_add_buddy_auth_with_gc_and_uid(gc_and_uid *g, const gchar *text) +static void request_add_buddy_auth_ex(PurpleConnection *gc, guint32 uid, + const gchar *text, guint8 *auth, guint8 auth_len) { - PurpleConnection *gc; - guint32 uid; - g_return_if_fail(g != NULL); + guint8 raw_data[MAX_PACKET_SIZE - 16]; + gint bytes = 0; - gc = g->gc; - uid = g->uid; g_return_if_fail(uid != 0); - _qq_send_packet_buddy_auth(gc, uid, QQ_MY_AUTH_REQUEST, text); - g_free(g); + bytes = 0; + bytes += qq_put8(raw_data + bytes, 0x02); + bytes += qq_put32(raw_data + bytes, uid); + bytes += qq_put16(raw_data + bytes, 0); + + bytes += qq_put8(raw_data + bytes, 0); + if (auth == NULL || auth_len <= 0) { + bytes += qq_put8(raw_data + bytes, 0); + } else { + bytes += qq_put8(raw_data + bytes, auth_len); + bytes += qq_putdata(raw_data + bytes, auth, auth_len); + } + bytes += qq_put8(raw_data + bytes, 1); /* ALLOW ADD ME FLAG */ + bytes += qq_put8(raw_data + bytes, 0); /* group number? */ + bytes += qq_put_vstr(raw_data + bytes, text, QQ_CHARSET_DEFAULT); + qq_send_cmd(gc, QQ_CMD_ADD_BUDDY_AUTH_EX, raw_data, bytes); +} + +void qq_process_add_buddy_auth_ex(PurpleConnection *gc, guint8 *data, gint data_len, guint32 ship32) +{ + g_return_if_fail(data != NULL && data_len != 0); + + qq_show_packet("qq_process_question", data, data_len); +} + +static void add_buddy_auth_cb(qq_buddy_req *add_req, const gchar *text) +{ + qq_data *qd; + g_return_if_fail(add_req != NULL); + if (add_req->gc == NULL || add_req->uid == 0) { + buddy_req_free(add_req); + return; + } + + qd = (qq_data *)add_req->gc->proto_data; + if (qd->client_version > 2005) { + request_add_buddy_auth_ex(add_req->gc, add_req->uid, + text, add_req->auth, add_req->auth_len); + } else { + request_add_buddy_auth(add_req->gc, add_req->uid, QQ_MY_AUTH_REQUEST, text); + } + buddy_req_free(add_req); } /* the real packet to reject and request is sent from here */ -static void _qq_reject_add_request_real(gc_and_uid *g, const gchar *reason) +static void buddy_add_deny_reason_cb(qq_buddy_req *add_req, const gchar *reason) { - gint uid; - PurpleConnection *gc; - - g_return_if_fail(g != NULL); + g_return_if_fail(add_req != NULL); + if (add_req->gc == NULL || add_req->uid == 0) { + buddy_req_free(add_req); + return; + } - gc = g->gc; - uid = g->uid; - g_return_if_fail(uid != 0); + request_add_buddy_auth(add_req->gc, add_req->uid, QQ_MY_AUTH_REJECT, reason); + buddy_req_free(add_req); +} - _qq_send_packet_buddy_auth(gc, uid, QQ_MY_AUTH_REJECT, reason); - g_free(g); +static void buddy_add_deny_noreason_cb(qq_buddy_req *add_req) +{ + buddy_add_deny_reason_cb(add_req, NULL); } /* we approve other's request of adding me as friend */ -void qq_approve_add_request_with_gc_and_uid(gc_and_uid *g) +static void buddy_add_authorize_cb(gpointer data) { - gint uid; - PurpleConnection *gc; - - g_return_if_fail(g != NULL); + qq_buddy_req *add_req = (qq_buddy_req *)data; - gc = g->gc; - uid = g->uid; - g_return_if_fail(uid != 0); + g_return_if_fail(add_req != NULL); + if (add_req->gc == NULL || add_req->uid == 0) { + buddy_req_free(add_req); + return; + } - _qq_send_packet_buddy_auth(gc, uid, QQ_MY_AUTH_APPROVE, NULL); - g_free(g); -} - -void qq_do_nothing_with_gc_and_uid(gc_and_uid *g, const gchar *msg) -{ - g_free(g); + request_add_buddy_auth(add_req->gc, add_req->uid, QQ_MY_AUTH_APPROVE, NULL); + buddy_req_free(add_req); } /* we reject other's request of adding me as friend */ -void qq_reject_add_request_with_gc_and_uid(gc_and_uid *g) +static void buddy_add_deny_cb(gpointer data) { - gint uid; - gchar *msg1, *msg2; - PurpleConnection *gc; - gc_and_uid *g2; - gchar *nombre; - - g_return_if_fail(g != NULL); - - gc = g->gc; - uid = g->uid; - g_return_if_fail(uid != 0); + qq_buddy_req *add_req = (qq_buddy_req *)data; + gchar *who = uid_to_purple_name(add_req->uid); + purple_request_input(add_req->gc, NULL, _("Authorization denied message:"), + NULL, _("Sorry, You are not my style."), TRUE, FALSE, NULL, + _("OK"), G_CALLBACK(buddy_add_deny_reason_cb), + _("Cancel"), G_CALLBACK(buddy_add_deny_noreason_cb), + purple_connection_get_account(add_req->gc), who, NULL, + add_req); + g_free(who); +} - g_free(g); - - g2 = g_new0(gc_and_uid, 1); - g2->gc = gc; - g2->uid = uid; +static void add_buddy_no_auth_cb(qq_buddy_req *add_req) +{ + qq_data *qd; + g_return_if_fail(add_req != NULL); + if (add_req->gc == NULL || add_req->uid == 0) { + buddy_req_free(add_req); + return; + } - msg1 = g_strdup_printf(_("You rejected %d's request"), uid); - msg2 = g_strdup(_("Message:")); - - nombre = uid_to_purple_name(uid); - purple_request_input(gc, _("Reject request"), msg1, msg2, - _("Sorry, you are not my style..."), TRUE, FALSE, - NULL, _("Reject"), G_CALLBACK(_qq_reject_add_request_real), _("Cancel"), NULL, - purple_connection_get_account(gc), nombre, NULL, - g2); - g_free(nombre); + qd = (qq_data *) add_req->gc->proto_data; + if (qd->client_version > 2005) { + request_add_buddy_no_auth_ex(add_req->gc, add_req->uid); + } else { + request_add_buddy_no_auth(add_req->gc, add_req->uid); + } + buddy_req_free(add_req); } -void qq_add_buddy_with_gc_and_uid(gc_and_uid *g) +void add_buddy_authorize_input(PurpleConnection *gc, guint32 uid, + guint8 *auth, guint8 auth_len) { - gint uid; - PurpleConnection *gc; - - g_return_if_fail(g != NULL); - - gc = g->gc; - uid = g->uid; + gchar *who, *msg; + qq_buddy_req *add_req; g_return_if_fail(uid != 0); - _qq_send_packet_add_buddy(gc, uid); - g_free(g); + add_req = g_new0(qq_buddy_req, 1); + add_req->gc = gc; + add_req->uid = uid; + add_req->auth = NULL; + add_req->auth_len = 0; + if (auth != NULL && auth_len > 0) { + add_req->auth = g_new0(guint8, auth_len); + g_memmove(add_req->auth, auth, auth_len); + add_req->auth_len = auth_len; + } + + who = uid_to_purple_name(uid); + msg = g_strdup_printf(_("%d needs authentication"), uid); + purple_request_input(gc, _("Add buddy authorize"), msg, + _("Input request here"), + _("Would you be my friend?"), + TRUE, FALSE, NULL, + _("Send"), G_CALLBACK(add_buddy_auth_cb), + _("Cancel"), G_CALLBACK(buddy_req_cancel_cb), + purple_connection_get_account(gc), who, NULL, + add_req); + + g_free(msg); + g_free(who); } -void qq_block_buddy_with_gc_and_uid(gc_and_uid *g) +/* add a buddy and send packet to QQ server + * note that when purple load local cached buddy list into its blist + * it also calls this funtion, so we have to + * define qd->is_login=TRUE AFTER LOGIN */ +void qq_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) { + qq_data *qd; guint32 uid; - PurpleConnection *gc; - PurpleBuddy buddy; - PurpleGroup group; + + g_return_if_fail(NULL != gc && NULL != gc->proto_data); + g_return_if_fail(buddy != NULL); - g_return_if_fail(g != NULL); + qd = (qq_data *) gc->proto_data; + if (!qd->is_login) + return; /* IMPORTANT ! */ - gc = g->gc; - uid = g->uid; - g_return_if_fail(uid > 0); + uid = purple_name_to_uid(buddy->name); + if (uid > 0) { + if (qd->client_version > 2005) { + request_add_buddy_no_auth_ex(gc, uid); + } else { + request_add_buddy_no_auth(gc, uid); + } + return; + } - buddy.name = uid_to_purple_name(uid); - group.name = PURPLE_GROUP_QQ_BLOCKED; + purple_notify_error(gc, _("QQ Buddy"), _("Add buddy"), _("Invalid QQ Number")); + if (buddy == NULL) { + return; + } - qq_remove_buddy(gc, &buddy, &group); - _qq_send_packet_remove_self_from(gc, uid); + purple_debug_info("QQ", "Remove buddy with invalid QQ number %d\n", uid); + qq_buddy_free(buddy); } /* process reply to add_buddy_auth request */ -void qq_process_add_buddy_auth_reply(guint8 *data, gint data_len, PurpleConnection *gc) +void qq_process_add_buddy_auth(guint8 *data, gint data_len, PurpleConnection *gc) { qq_data *qd; gchar **segments, *msg_utf8; @@ -257,301 +720,576 @@ qd = (qq_data *) gc->proto_data; - if (data[0] != QQ_ADD_BUDDY_AUTH_REPLY_OK) { - purple_debug_warning("QQ", "Add buddy with auth request failed\n"); - if (NULL == (segments = split_data(data, data_len, "\x1f", 2))) { - return; - } - msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT); - purple_notify_error(gc, NULL, _("Add buddy with auth request failed"), msg_utf8); - g_free(msg_utf8); - } else { - purple_debug_info("QQ", "Add buddy with auth request OK\n"); + if (data[0] == '0') { + purple_debug_info("QQ", "Reply OK for sending authorize\n"); + return; } + + if (NULL == (segments = split_data(data, data_len, "\x1f", 2))) { + purple_notify_error(gc, _("QQ Buddy"), _("Failed sending authorize"), NULL); + return; + } + msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT); + purple_notify_error(gc, _("QQ Buddy"), _("Failed sending authorize"), msg_utf8); + g_free(msg_utf8); } /* process the server reply for my request to remove a buddy */ -void qq_process_remove_buddy_reply(guint8 *data, gint data_len, PurpleConnection *gc) +void qq_process_remove_buddy(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid) { - qq_data *qd; + PurpleBuddy *buddy = NULL; + gchar *msg; g_return_if_fail(data != NULL && data_len != 0); - - qd = (qq_data *) gc->proto_data; + g_return_if_fail(uid != 0); - if (data[0] != QQ_REMOVE_BUDDY_REPLY_OK) { - /* there is no reason return from server */ - purple_debug_warning("QQ", "Remove buddy fails\n"); - purple_notify_info(gc, _("QQ Buddy"), _("Failed:"), _("Remove buddy")); - } else { /* if reply */ - purple_debug_info("QQ", "Remove buddy OK\n"); - /* TODO: We don't really need to notify the user about this, do we? */ - purple_notify_info(gc, _("QQ Buddy"), _("Successed:"), _("Remove buddy")); + buddy = qq_buddy_find(gc, uid); + if (data[0] != 0) { + msg = g_strdup_printf(_("Failed removing buddy %d"), uid); + purple_notify_info(gc, _("QQ Buddy"), msg, NULL); + g_free(msg); + } + + purple_debug_info("QQ", "Reply OK for removing buddy\n"); + /* remove buddy again */ + if (buddy != NULL) { + qq_buddy_free(buddy); } } /* process the server reply for my request to remove myself from a buddy */ -void qq_process_remove_self_reply(guint8 *data, gint data_len, PurpleConnection *gc) +void qq_process_buddy_remove_me(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid) { qq_data *qd; + gchar *msg; g_return_if_fail(data != NULL && data_len != 0); + qd = (qq_data *) gc->proto_data; + + if (data[0] == 0) { + purple_debug_info("QQ", "Reply OK for removing me from %d's buddy list\n", uid); + return; + } + msg = g_strdup_printf(_("Failed removing me from %d's buddy list"), uid); + purple_notify_info(gc, _("QQ Buddy"), msg, NULL); + g_free(msg); +} + +void qq_process_add_buddy_no_auth(PurpleConnection *gc, + guint8 *data, gint data_len, guint32 uid) +{ + qq_data *qd; + gchar **segments; + gchar *dest_uid, *reply; + PurpleBuddy *buddy; + + g_return_if_fail(data != NULL && data_len != 0); + g_return_if_fail(uid != 0); qd = (qq_data *) gc->proto_data; - if (data[0] != QQ_REMOVE_SELF_REPLY_OK) { - /* there is no reason return from server */ - purple_debug_warning("QQ", "Remove self fails\n"); - purple_notify_info(gc, _("QQ Buddy"), _("Failed:"), _("Remove from other's buddy list")); - } else { /* if reply */ - purple_debug_info("QQ", "Remove from a buddy OK\n"); - /* TODO: Does the user really need to be notified about this? */ - purple_notify_info(gc, _("QQ Buddy"), _("Successed:"), _("Remove from other's buddy list")); - } -} - -void qq_process_add_buddy_reply(guint8 *data, gint data_len, guint16 seq, PurpleConnection *gc) -{ - qq_data *qd; - gint for_uid; - gchar *msg, **segments, *uid, *reply; - GList *list; - PurpleBuddy *b; - gc_and_uid *g; - qq_add_buddy_request *req; - gchar *nombre; - - g_return_if_fail(data != NULL && data_len != 0); - - for_uid = 0; - qd = (qq_data *) gc->proto_data; - - list = qd->add_buddy_request; - while (list != NULL) { - req = (qq_add_buddy_request *) list->data; - if (req->seq == seq) { /* reply to this */ - for_uid = req->uid; - qd->add_buddy_request = g_list_remove(qd->add_buddy_request, qd->add_buddy_request->data); - g_free(req); - break; - } - list = list->next; - } - - if (for_uid == 0) { /* we have no record for this */ - purple_debug_error("QQ", "We have no record for add buddy reply [%d], discard\n", seq); - return; - } else { - purple_debug_info("QQ", "Add buddy reply [%d] is for id [%d]\n", seq, for_uid); - } + purple_debug_info("QQ", "Process buddy add for id [%d]\n", uid); + qq_show_packet("buddy_add_no_auth", data, data_len); if (NULL == (segments = split_data(data, data_len, "\x1f", 2))) return; - uid = segments[0]; + dest_uid = segments[0]; reply = segments[1]; - if (strtol(uid, NULL, 10) != qd->uid) { /* should not happen */ - purple_debug_error("QQ", "Add buddy reply is to [%s], not me!", uid); + if (strtol(dest_uid, NULL, 10) != qd->uid) { /* should not happen */ + purple_debug_error("QQ", "Add buddy reply is to [%s], not me!", dest_uid); g_strfreev(segments); return; } - if (strtol(reply, NULL, 10) > 0) { /* need auth */ - purple_debug_warning("QQ", "Add buddy attempt fails, need authentication\n"); - nombre = uid_to_purple_name(for_uid); - b = purple_find_buddy(gc->account, nombre); - if (b != NULL) - purple_blist_remove_buddy(b); - g = g_new0(gc_and_uid, 1); - g->gc = gc; - g->uid = for_uid; - msg = g_strdup_printf(_("%d needs authentication"), for_uid); - purple_request_input(gc, NULL, msg, - _("Input request here"), /* TODO: Awkward string to fix post string freeze - standardize auth dialogues? -evands */ - _("Would you be my friend?"), - TRUE, FALSE, NULL, _("Send"), - G_CALLBACK - (_qq_send_packet_add_buddy_auth_with_gc_and_uid), - _("Cancel"), G_CALLBACK(qq_do_nothing_with_gc_and_uid), - purple_connection_get_account(gc), nombre, NULL, - g); - g_free(msg); - g_free(nombre); - } else { /* add OK */ - qq_add_buddy_by_recv_packet(gc, for_uid, TRUE, TRUE); - msg = g_strdup_printf(_("Add into %d's buddy list"), for_uid); - purple_notify_info(gc, _("QQ Buddy"), _("Successed:"), msg); - g_free(msg); + if (strtol(reply, NULL, 10) == 0) { + /* add OK */ + qq_buddy_find_or_new(gc, uid); + + qq_request_buddy_info(gc, uid, 0, 0); + if (qd->client_version >= 2007) { + qq_request_get_level_2007(gc, uid); + } else { + qq_request_get_level(gc, uid); + } + qq_request_get_buddies_online(gc, 0, 0); + + purple_debug_info("QQ", "Successed adding into %d's buddy list", uid); + g_strfreev(segments); + return; } + + /* need auth */ + purple_debug_warning("QQ", "Failed adding buddy, need authorize\n"); + + buddy = qq_buddy_find(gc, uid); + if (buddy == NULL) { + buddy = qq_buddy_new(gc, uid); + } + if (buddy != NULL && buddy->proto_data != NULL) { + /* Not authorized now, free buddy data */ + qq_buddy_data_free(buddy->proto_data); + buddy->proto_data = NULL; + } + + add_buddy_authorize_input(gc, uid, NULL, 0); g_strfreev(segments); } -PurpleGroup *qq_get_purple_group(const gchar *group_name) +void qq_process_add_buddy_no_auth_ex(PurpleConnection *gc, + guint8 *data, gint data_len, guint32 uid) { - PurpleGroup *g; + qq_data *qd; + gint bytes; + guint32 dest_uid; + guint8 reply; + guint8 auth_type; - g_return_val_if_fail(group_name != NULL, NULL); + g_return_if_fail(data != NULL && data_len >= 5); + g_return_if_fail(uid != 0); + + qd = (qq_data *) gc->proto_data; + + purple_debug_info("QQ", "Process buddy add no auth for id [%d]\n", uid); + qq_show_packet("buddy_add_no_auth_ex", data, data_len); - g = purple_find_group(group_name); - if (g == NULL) { - g = purple_group_new(group_name); - purple_blist_add_group(g, NULL); - purple_debug_warning("QQ", "Add new group: %s\n", group_name); + bytes = 0; + bytes += qq_get32(&dest_uid, data + bytes); + bytes += qq_get8(&reply, data + bytes); + + g_return_if_fail(dest_uid == uid); + + if (reply == 0x99) { + purple_debug_info("QQ", "Successed adding buddy %d\n", uid); + qq_buddy_find_or_new(gc, uid); + + qq_request_buddy_info(gc, uid, 0, 0); + if (qd->client_version >= 2007) { + qq_request_get_level_2007(gc, uid); + } else { + qq_request_get_level(gc, uid); + } + qq_request_get_buddies_online(gc, 0, 0); + return; } - return g; -} - -/* we add new buddy, if the received packet is from someone not in my list - * return the PurpleBuddy that is just created */ -PurpleBuddy *qq_add_buddy_by_recv_packet(PurpleConnection *gc, guint32 uid, gboolean is_known, gboolean create) -{ - PurpleAccount *a; - PurpleBuddy *b; - PurpleGroup *g; - qq_data *qd; - qq_buddy *q_bud; - gchar *name, *group_name; - - a = gc->account; - qd = (qq_data *) gc->proto_data; - g_return_val_if_fail(a != NULL && uid != 0, NULL); - - group_name = is_known ? - g_strdup_printf(PURPLE_GROUP_QQ_FORMAT, purple_account_get_username(a)) : g_strdup(PURPLE_GROUP_QQ_UNKNOWN); - - g = qq_get_purple_group(group_name); - - name = uid_to_purple_name(uid); - b = purple_find_buddy(gc->account, name); - /* remove old, we can not simply return here - * because there might be old local copy of this buddy */ - if (b != NULL) - purple_blist_remove_buddy(b); - - b = purple_buddy_new(a, name, NULL); - - if (!create) - b->proto_data = NULL; - else { - q_bud = g_new0(qq_buddy, 1); - q_bud->uid = uid; - b->proto_data = q_bud; - qd->buddies = g_list_append(qd->buddies, q_bud); - qq_send_packet_get_info(gc, q_bud->uid, FALSE); - qq_request_get_buddies_online(gc, 0, 0); + if (reply != 0) { + purple_debug_info("QQ", "Failed adding buddy %d, Unknow reply 0x%02X\n", + uid, reply); } - purple_blist_add_buddy(b, NULL, g, NULL); - purple_debug_warning("QQ", "Add new buddy: [%s]\n", name); - - g_free(name); - g_free(group_name); - - return b; -} + /* need auth */ + g_return_if_fail(data_len > bytes); + bytes += qq_get8(&auth_type, data + bytes); + purple_debug_warning("QQ", "Adding buddy needs authorize 0x%02X\n", auth_type); -/* add a buddy and send packet to QQ server - * note that when purple load local cached buddy list into its blist - * it also calls this funtion, so we have to - * define qd->is_login=TRUE AFTER serv_finish_login(gc) */ -void qq_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) -{ - qq_data *qd; - guint32 uid; - PurpleBuddy *b; - - qd = (qq_data *) gc->proto_data; - if (!qd->is_login) - return; /* IMPORTANT ! */ - - uid = purple_name_to_uid(buddy->name); - if (uid > 0) - _qq_send_packet_add_buddy(gc, uid); - else { - b = purple_find_buddy(gc->account, buddy->name); - if (b != NULL) - purple_blist_remove_buddy(b); - purple_notify_error(gc, NULL, - _("QQ Number Error"), - _("Invalid QQ Number")); + switch (auth_type) { + case 0x00: /* no authorize */ + break; + case 0x01: /* authorize */ + qq_request_auth_code(gc, QQ_AUTH_INFO_BUDDY, QQ_AUTH_INFO_ADD_BUDDY, uid); + break; + case 0x02: /* disable */ + break; + case 0x03: /* answer question */ + qq_request_question(gc, QQ_QUESTION_REQUEST, uid, NULL, NULL); + break; + default: + g_return_if_reached(); + break; } + return; } /* remove a buddy and send packet to QQ server accordingly */ void qq_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) { qq_data *qd; - PurpleBuddy *b; - qq_buddy *q_bud; guint32 uid; + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(buddy != NULL); + qd = (qq_data *) gc->proto_data; - uid = purple_name_to_uid(buddy->name); - if (!qd->is_login) return; - if (uid > 0) - _qq_send_packet_remove_buddy(gc, uid); + uid = purple_name_to_uid(buddy->name); + if (uid > 0 && uid != qd->uid) { + if (qd->client_version > 2005) { + qq_request_auth_code(gc, QQ_AUTH_INFO_BUDDY, QQ_AUTH_INFO_REMOVE_BUDDY, uid); + } else { + request_remove_buddy(gc, uid); + request_buddy_remove_me(gc, uid); + } + } + + if (buddy->proto_data) { + qq_buddy_data_free(buddy->proto_data); + buddy->proto_data = NULL; + } else { + purple_debug_warning("QQ", "Empty buddy data of %s\n", buddy->name); + } + + /* Do not call purple_blist_remove_buddy, + * otherwise purple segmentation fault */ +} + +static void buddy_add_input(PurpleConnection *gc, guint32 uid, gchar *reason) +{ + PurpleAccount *account = purple_connection_get_account(gc); + qq_buddy_req *add_req; + gchar *who; + + g_return_if_fail(uid != 0 && reason != NULL); + + purple_debug_info("QQ", "Buddy %d request adding, msg: %s\n", uid, reason); + + add_req = g_new0(qq_buddy_req, 1); + add_req->gc = gc; + add_req->uid = uid; + + if (purple_prefs_get_bool("/plugins/prpl/qq/auto_get_authorize_info")) { + qq_request_buddy_info(gc, add_req->uid, 0, QQ_BUDDY_INFO_DISPLAY); + } + who = uid_to_purple_name(add_req->uid); + + purple_account_request_authorization(account, + who, NULL, + NULL, reason, + purple_find_buddy(account, who) != NULL, + buddy_add_authorize_cb, + buddy_add_deny_cb, + add_req); + + g_free(who); +} + +/* someone wants to add you to his buddy list */ +static void server_buddy_add_request(PurpleConnection *gc, gchar *from, gchar *to, + guint8 *data, gint data_len) +{ + guint32 uid; + gchar *msg, *reason; + + g_return_if_fail(from != NULL && to != NULL); + uid = strtol(from, NULL, 10); + g_return_if_fail(uid != 0); + + if (purple_prefs_get_bool("/plugins/prpl/qq/auto_get_authorize_info")) { + qq_request_buddy_info(gc, uid, 0, QQ_BUDDY_INFO_DISPLAY); + } + + if (data_len <= 0) { + reason = g_strdup( _("No reason given") ); + } else { + msg = g_strndup((gchar *)data, data_len); + reason = qq_to_utf8(msg, QQ_CHARSET_DEFAULT); + if (reason == NULL) reason = g_strdup( _("Unknown reason") ); + g_free(msg); + } + + buddy_add_input(gc, uid, reason); + g_free(reason); +} + +void qq_process_buddy_check_code(PurpleConnection *gc, guint8 *data, gint data_len) +{ + qq_data *qd; + gint bytes; + guint8 cmd; + guint8 reply; + guint32 uid; + guint16 flag1, flag2; + + g_return_if_fail(data != NULL && data_len >= 5); + g_return_if_fail(uid != 0); + + qd = (qq_data *) gc->proto_data; + + qq_show_packet("buddy_check_code", data, data_len); + + bytes = 0; + bytes += qq_get8(&cmd, data + bytes); /* 0x03 */ + bytes += qq_get8(&reply, data + bytes); + + if (reply == 0) { + purple_debug_info("QQ", "Failed checking code\n"); + return; + } + + bytes += qq_get32(&uid, data + bytes); + g_return_if_fail(uid != 0); + bytes += qq_get16(&flag1, data + bytes); + bytes += qq_get16(&flag2, data + bytes); + purple_debug_info("QQ", "Check code reply Ok, uid %d, flag 0x%04X-0x%04X\n", + uid, flag1, flag2); + return; +} + +static void request_buddy_check_code(PurpleConnection *gc, + gchar *from, guint8 *code, gint code_len) +{ + guint8 *raw_data; + gint bytes; + guint32 uid; + + g_return_if_fail(code != NULL && code_len > 0 && from != NULL); + + uid = strtol(from, NULL, 10); + raw_data = g_newa(guint8, code_len + 16); + bytes = 0; + bytes += qq_put8(raw_data + bytes, 0x03); + bytes += qq_put8(raw_data + bytes, 0x01); + bytes += qq_put32(raw_data + bytes, uid); + bytes += qq_put16(raw_data + bytes, code_len); + bytes += qq_putdata(raw_data + bytes, code, code_len); + + qq_send_cmd(gc, QQ_CMD_BUDDY_CHECK_CODE, raw_data, bytes); +} + +static gint server_buddy_check_code(PurpleConnection *gc, + gchar *from, guint8 *data, gint data_len) +{ + gint bytes; + guint16 code_len; + guint8 *code; + + g_return_val_if_fail(data != NULL && data_len > 0, 0); + + bytes = 0; + bytes += qq_get16(&code_len, data + bytes); + if (code_len <= 0) { + purple_debug_info("QQ", "Server msg for buddy has no code\n"); + return bytes; + } + if (bytes + code_len < data_len) { + purple_debug_error("QQ", "Code len error in server msg for buddy\n"); + qq_show_packet("server_buddy_check_code", data, data_len); + code_len = data_len - bytes; + } + code = g_newa(guint8, code_len); + bytes += qq_getdata(code, code_len, data + bytes); + + request_buddy_check_code(gc, from, code, code_len); + return bytes; +} + +static void server_buddy_add_request_ex(PurpleConnection *gc, gchar *from, gchar *to, + guint8 *data, gint data_len) +{ + gint bytes; + guint32 uid; + gchar *msg; + guint8 allow_reverse; - b = purple_find_buddy(gc->account, buddy->name); - if (b != NULL) { - q_bud = (qq_buddy *) b->proto_data; - if (q_bud != NULL) - qd->buddies = g_list_remove(qd->buddies, q_bud); - else - purple_debug_warning("QQ", "We have no qq_buddy record for %s\n", buddy->name); - /* remove buddy on blist, this does not trigger qq_remove_buddy again - * do this only if the request comes from block request, - * otherwise purple segmentation fault */ - if (g_ascii_strcasecmp(group->name, PURPLE_GROUP_QQ_BLOCKED) == 0) - purple_blist_remove_buddy(b); + g_return_if_fail(from != NULL && to != NULL); + g_return_if_fail(data != NULL && data_len >= 3); + uid = strtol(from, NULL, 10); + g_return_if_fail(uid != 0); + + /* qq_show_packet("server_buddy_add_request_ex", data, data_len); */ + + bytes = 0; + bytes += qq_get_vstr(&msg, QQ_CHARSET_DEFAULT, data+bytes); + bytes += qq_get8(&allow_reverse, data + bytes); /* allow_reverse = 0x01, allowed */ + server_buddy_check_code(gc, from, data + bytes, data_len - bytes); + + if (strlen(msg) <= 0) { + g_free(msg); + msg = g_strdup( _("No reason given") ); + } + buddy_add_input(gc, uid, msg); + g_free(msg); +} + +/* when you are added by a person, QQ server will send sys message */ +static void server_buddy_added(PurpleConnection *gc, gchar *from, gchar *to, + guint8 *data, gint data_len) +{ + PurpleAccount *account = purple_connection_get_account(gc); + PurpleBuddy *buddy; + guint32 uid; + qq_buddy_req *add_req; + gchar *who; + gchar *primary; + + g_return_if_fail(from != NULL && to != NULL); + + uid = strtol(from, NULL, 10); + who = uid_to_purple_name(uid); + + buddy = purple_find_buddy(account, who); + if (buddy != NULL) { + purple_account_notify_added(account, from, to, NULL, NULL); + } + + add_req = g_new0(qq_buddy_req, 1); + add_req->gc = gc; + add_req->uid = uid; /* only need to get value */ + primary = g_strdup_printf(_("You have been added by %s"), from); + purple_request_action(gc, NULL, primary, + _("Would you like to add him?"), + PURPLE_DEFAULT_ACTION_NONE, + purple_connection_get_account(gc), who, NULL, + add_req, 2, + _("Add"), G_CALLBACK(add_buddy_no_auth_cb), + _("Cancel"), G_CALLBACK(buddy_req_cancel_cb)); + + g_free(who); + g_free(primary); +} + +static void server_buddy_added_ex(PurpleConnection *gc, gchar *from, gchar *to, + guint8 *data, gint data_len) +{ + gint bytes; + guint8 allow_reverse; + gchar *msg; + + g_return_if_fail(from != NULL && to != NULL); + g_return_if_fail(data != NULL && data_len >= 3); + + qq_show_packet("server_buddy_added_ex", data, data_len); + + bytes = 0; + bytes += qq_get_vstr(&msg, QQ_CHARSET_DEFAULT, data+bytes); /* always empty msg */ + purple_debug_info("QQ", "Buddy added msg: %s\n", msg); + bytes += qq_get8(&allow_reverse, data + bytes); /* allow_reverse = 0x01, allowed */ + server_buddy_check_code(gc, from, data + bytes, data_len - bytes); + + g_free(msg); +} + +static void server_buddy_adding_ex(PurpleConnection *gc, gchar *from, gchar *to, + guint8 *data, gint data_len) +{ + gint bytes; + guint8 allow_reverse; + + g_return_if_fail(from != NULL && to != NULL); + g_return_if_fail(data != NULL && data_len >= 3); + + qq_show_packet("server_buddy_adding_ex", data, data_len); + + bytes = 0; + bytes += qq_get8(&allow_reverse, data + bytes); /* allow_reverse = 0x01, allowed */ + server_buddy_check_code(gc, from, data + bytes, data_len - bytes); +} + +/* the buddy approves your request of adding him/her as your friend */ +static void server_buddy_added_me(PurpleConnection *gc, gchar *from, gchar *to, + guint8 *data, gint data_len) +{ + PurpleAccount *account = purple_connection_get_account(gc); + qq_data *qd; + guint32 uid; + + g_return_if_fail(from != NULL && to != NULL); + + qd = (qq_data *) gc->proto_data; + + uid = strtol(from, NULL, 10); + g_return_if_fail(uid > 0); + + server_buddy_check_code(gc, from, data, data_len); + + qq_buddy_find_or_new(gc, uid); + qq_request_buddy_info(gc, uid, 0, 0); + qq_request_get_buddies_online(gc, 0, 0); + if (qd->client_version >= 2007) { + qq_request_get_level_2007(gc, uid); + } else { + qq_request_get_level(gc, uid); + } + + purple_account_notify_added(account, to, from, NULL, NULL); +} + +/* you are rejected by the person */ +static void server_buddy_rejected_me(PurpleConnection *gc, gchar *from, gchar *to, + guint8 *data, gint data_len) +{ + guint32 uid; + PurpleBuddy *buddy; + gchar *msg, *msg_utf8; + gint bytes; + gchar **segments; + gchar *primary, *secondary; + + g_return_if_fail(from != NULL && to != NULL); + + qq_show_packet("server_buddy_rejected_me", data, data_len); + + if (data_len <= 0) { + msg = g_strdup( _("No reason given") ); + } else { + segments = g_strsplit((gchar *)data, "\x1f", 1); + if (segments != NULL && segments[0] != NULL) { + msg = g_strdup(segments[0]); + g_strfreev(segments); + bytes = strlen(msg) + 1; + if (bytes < data_len) { + server_buddy_check_code(gc, from, data + bytes, data_len - bytes); + } + } else { + msg = g_strdup( _("No reason given") ); + } + } + msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT); + if (msg_utf8 == NULL) { + msg_utf8 = g_strdup( _("Unknown reason") ); + } + g_free(msg); + + primary = g_strdup_printf(_("Rejected by %s"), from); + secondary = g_strdup_printf(_("Message: %s"), msg_utf8); + + purple_notify_info(gc, _("QQ Buddy"), primary, secondary); + + g_free(msg_utf8); + g_free(primary); + g_free(secondary); + + uid = strtol(from, NULL, 10); + g_return_if_fail(uid != 0); + + buddy = qq_buddy_find(gc, uid); + if (buddy != NULL && buddy->proto_data != NULL) { + /* Not authorized now, free buddy data */ + qq_buddy_data_free(buddy->proto_data); + buddy->proto_data = NULL; } } -/* free add buddy request queue */ -void qq_add_buddy_request_free(qq_data *qd) +void qq_process_buddy_from_server(PurpleConnection *gc, int funct, + gchar *from, gchar *to, guint8 *data, gint data_len) { - gint count; - qq_add_buddy_request *p; - - count = 0; - while (qd->add_buddy_request != NULL) { - p = (qq_add_buddy_request *) (qd->add_buddy_request->data); - qd->add_buddy_request = g_list_remove(qd->add_buddy_request, p); - g_free(p); - count++; - } - if (count > 0) { - purple_debug_info("QQ", "%d add buddy requests are freed!\n", count); + switch (funct) { + case QQ_SERVER_BUDDY_ADDED: + server_buddy_added(gc, from, to, data, data_len); + break; + case QQ_SERVER_BUDDY_ADD_REQUEST: + server_buddy_add_request(gc, from, to, data, data_len); + break; + case QQ_SERVER_BUDDY_ADD_REQUEST_EX: + server_buddy_add_request_ex(gc, from, to, data, data_len); + break; + case QQ_SERVER_BUDDY_ADDED_ME: + server_buddy_added_me(gc, from, to, data, data_len); + break; + case QQ_SERVER_BUDDY_REJECTED_ME: + server_buddy_rejected_me(gc, from, to, data, data_len); + break; + case QQ_SERVER_BUDDY_ADDED_EX: + server_buddy_added_ex(gc, from, to, data, data_len); + break; + case QQ_SERVER_BUDDY_ADDING_EX: + case QQ_SERVER_BUDDY_ADDED_ANSWER: + server_buddy_adding_ex(gc, from, to, data, data_len); + break; + default: + purple_debug_warning("QQ", "Unknow buddy operate (%d) from server\n", funct); + break; } } - -/* free up all qq_buddy */ -void qq_buddies_list_free(PurpleAccount *account, qq_data *qd) -{ - gint count; - qq_buddy *p; - gchar *name; - PurpleBuddy *b; - - count = 0; - while (qd->buddies) { - p = (qq_buddy *) (qd->buddies->data); - qd->buddies = g_list_remove(qd->buddies, p); - name = uid_to_purple_name(p->uid); - b = purple_find_buddy(account, name); - if(b != NULL) - b->proto_data = NULL; - else - purple_debug_info("QQ", "qq_buddy %s not found in purple proto_data\n", name); - g_free(name); - - g_free(p); - count++; - } - if (count > 0) { - purple_debug_info("QQ", "%d qq_buddy structures are freed!\n", count); - } -} diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/buddy_opt.h --- a/libpurple/protocols/qq/buddy_opt.h Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/buddy_opt.h Wed Oct 29 21:38:53 2008 +0000 @@ -30,33 +30,55 @@ #include "qq.h" -typedef struct _gc_and_uid gc_and_uid; +enum { + QQ_AUTH_INFO_BUDDY = 0x01, + QQ_AUTH_INFO_ROOM = 0x02, -struct _gc_and_uid { - guint32 uid; - PurpleConnection *gc; + QQ_AUTH_INFO_ADD_BUDDY = 0x0001, + QQ_AUTH_INFO_TEMP_SESSION = 0x0003, + QQ_AUTH_INFO_CLUSTER = 0x0002, + QQ_AUTH_INFO_REMOVE_BUDDY = 0x0006, + QQ_AUTH_INFO_UPDATE_BUDDY_INFO = 0x0007, +}; + +enum { + QQ_QUESTION_GET = 0x01, + QQ_QUESTION_SET = 0x02, + QQ_QUESTION_REQUEST = 0x03, /* get question only*/ + QQ_QUESTION_ANSWER = 0x04, }; -void qq_approve_add_request_with_gc_and_uid(gc_and_uid *g); -void qq_reject_add_request_with_gc_and_uid(gc_and_uid *g); +void qq_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group); +void qq_change_buddys_group(PurpleConnection *gc, const char *who, + const char *old_group, const char *new_group); +void qq_remove_buddy_and_me(PurpleBlistNode * node); +void qq_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group); -void qq_add_buddy_with_gc_and_uid(gc_and_uid *g); -void qq_block_buddy_with_gc_and_uid(gc_and_uid *g); - -void qq_do_nothing_with_gc_and_uid(gc_and_uid *g, const gchar *msg); +void qq_process_remove_buddy(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid); +void qq_process_buddy_remove_me(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid); +void qq_process_add_buddy_no_auth(PurpleConnection *gc, + guint8 *data, gint data_len, guint32 uid); +void qq_process_add_buddy_no_auth_ex(PurpleConnection *gc, + guint8 *data, gint data_len, guint32 uid); +void qq_process_add_buddy_auth(guint8 *data, gint data_len, PurpleConnection *gc); +void qq_process_buddy_from_server(PurpleConnection *gc, int funct, + gchar *from, gchar *to, guint8 *data, gint data_len); -void qq_process_remove_buddy_reply(guint8 *buf, gint buf_len, PurpleConnection *gc); -void qq_process_remove_self_reply(guint8 *data, gint data_len, PurpleConnection *gc); -void qq_process_add_buddy_reply(guint8 *data, gint data_len, guint16 seq, PurpleConnection *gc); -void qq_process_add_buddy_auth_reply(guint8 *data, gint data_len, PurpleConnection *gc); -PurpleBuddy *qq_add_buddy_by_recv_packet(PurpleConnection *gc, guint32 uid, gboolean is_known, gboolean create); -void qq_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group); +void qq_process_buddy_check_code(PurpleConnection *gc, guint8 *data, gint data_len); + +void qq_request_auth_code(PurpleConnection *gc, guint8 cmd, guint16 sub_cmd, guint32 uid); +void qq_process_auth_code(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid); +void qq_request_question(PurpleConnection *gc, + guint8 cmd, guint32 uid, const gchar *question_utf8, const gchar *answer_utf8); +void qq_process_question(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid); -PurpleGroup *qq_get_purple_group(const gchar *group_name); +void qq_process_add_buddy_auth_ex(PurpleConnection *gc, guint8 *data, gint data_len, guint32 ship32); + +qq_buddy_data *qq_buddy_data_find(PurpleConnection *gc, guint32 uid); +void qq_buddy_data_free(qq_buddy_data *bd); -void qq_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group); -void qq_add_buddy_request_free(qq_data *qd); - -void qq_buddies_list_free(PurpleAccount *account, qq_data *qd); - +PurpleBuddy *qq_buddy_new(PurpleConnection *gc, guint32 uid); +PurpleBuddy *qq_buddy_find_or_new(PurpleConnection *gc, guint32 uid); +PurpleBuddy *qq_buddy_find(PurpleConnection *gc, guint32 uid); +PurpleGroup *qq_group_find_or_new(const gchar *group_name); #endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/char_conv.c --- a/libpurple/protocols/qq/char_conv.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/char_conv.c Wed Oct 29 21:38:53 2008 +0000 @@ -37,7 +37,7 @@ #define QQ_CHARSET_ENG "ISO-8859-1" #define QQ_NULL_MSG "(NULL)" /* return this if conversion fails */ -#define QQ_NULL_SMILEY "(Broken)" /* return this if smiley conversion fails */ +#define QQ_NULL_SMILEY "" /* return this if smiley conversion fails */ const gchar qq_smiley_map[QQ_SMILEY_AMOUNT] = { 0x41, 0x43, 0x42, 0x44, 0x45, 0x46, 0x47, 0x48, @@ -98,7 +98,7 @@ } /* convert a string from from_charset to to_charset, using g_convert */ -static gchar *_my_convert(const gchar *str, gssize len, const gchar *to_charset, const gchar *from_charset) +static gchar *do_convert(const gchar *str, gssize len, const gchar *to_charset, const gchar *from_charset) { GError *error = NULL; gchar *ret; @@ -128,20 +128,42 @@ * returns the number of bytes read, return -1 if fatal error * the converted UTF-8 will be saved in ret */ -gint convert_as_pascal_string(guint8 *data, gchar **ret, const gchar *from_charset) +gint qq_get_vstr(gchar **ret, const gchar *from_charset, guint8 *data) { guint8 len; g_return_val_if_fail(data != NULL && from_charset != NULL, -1); len = data[0]; - *ret = _my_convert((gchar *) (data + 1), (gssize) len, UTF8, from_charset); + if (len == 0) { + *ret = g_strdup(""); + return 1; + } + *ret = do_convert((gchar *) (data + 1), (gssize) len, UTF8, from_charset); return len + 1; } +gint qq_put_vstr(guint8 *buf, const gchar *str_utf8, const gchar *to_charset) +{ + gchar *str; + guint8 len; + + if (str_utf8 == NULL || (len = strlen(str_utf8)) == 0) { + buf[0] = 0; + return 1; + } + str = do_convert(str_utf8, -1, to_charset, UTF8); + len = strlen(str_utf8); + buf[0] = len; + if (len > 0) { + memcpy(buf + 1, str, len); + } + return 1 + len; +} + /* convert QQ formatted msg to Purple formatted msg (and UTF-8) */ -gchar *qq_encode_to_purple(guint8 *data, gint len, const gchar *msg) +gchar *qq_encode_to_purple(guint8 *data, gint len, const gchar *msg, const gint client_version) { GString *encoded; guint8 font_attr, font_size, color[3], bar; @@ -211,15 +233,15 @@ return ret; } -/* two convenience methods, using _my_convert */ +/* two convenience methods, using do_convert */ gchar *utf8_to_qq(const gchar *str, const gchar *to_charset) { - return _my_convert(str, -1, to_charset, UTF8); + return do_convert(str, -1, to_charset, UTF8); } gchar *qq_to_utf8(const gchar *str, const gchar *from_charset) { - return _my_convert(str, -1, UTF8, from_charset); + return do_convert(str, -1, UTF8, from_charset); } /* QQ uses binary code for smiley, while purple uses strings. @@ -232,8 +254,10 @@ converted = g_string_new(""); segments = split_data((guint8 *) text, strlen(text), "\x14\x15", 0); + if(segments == NULL) + return NULL; + g_string_append(converted, segments[0]); - while ((*(++segments)) != NULL) { cur_seg = *segments; qq_smiley = cur_seg[0]; @@ -291,5 +315,3 @@ } g_strstrip(str); } - - diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/char_conv.h --- a/libpurple/protocols/qq/char_conv.h Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/char_conv.h Wed Oct 29 21:38:53 2008 +0000 @@ -29,15 +29,15 @@ #define QQ_CHARSET_DEFAULT "GB18030" -gint convert_as_pascal_string(guint8 *data, gchar **ret, const gchar *from_charset); +gint qq_get_vstr(gchar **ret, const gchar *from_charset, guint8 *data); +gint qq_put_vstr(guint8 *buf, const gchar *str_utf8, const gchar *to_charset); gchar *qq_smiley_to_purple(gchar *text); - gchar *purple_smiley_to_qq(gchar *text); gchar *utf8_to_qq(const gchar *str, const gchar *to_charset); gchar *qq_to_utf8(const gchar *str, const gchar *from_charset); -gchar *qq_encode_to_purple(guint8 *font_attr_data, gint len, const gchar *msg); +gchar *qq_encode_to_purple(guint8 *font_attr_data, gint len, const gchar *msg, const gint client_version); gchar *qq_im_filter_html(const gchar *text); void qq_filter_str(gchar *str); diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/file_trans.c --- a/libpurple/protocols/qq/file_trans.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/file_trans.c Wed Oct 29 21:38:53 2008 +0000 @@ -30,7 +30,7 @@ #include "qq_crypt.h" #include "file_trans.h" -#include "header_info.h" +#include "qq_define.h" #include "im.h" #include "packet_parse.h" #include "proxy.h" @@ -81,7 +81,7 @@ const gint QQ_MAX_FILE_MD5_LENGTH = 10002432; g_return_if_fail(filename != NULL && md5 != NULL); - if (filelen > QQ_MAX_FILE_MD5_LENGTH) + if (filelen > QQ_MAX_FILE_MD5_LENGTH) filelen = QQ_MAX_FILE_MD5_LENGTH; fp = fopen(filename, "rb"); @@ -161,7 +161,7 @@ fd = open(purple_xfer_get_local_filename(xfer), O_RDONLY); info->buffer = mmap(0, purple_xfer_get_size(xfer), PROT_READ, MAP_PRIVATE, fd, 0); } - else + else { fd = open(purple_xfer_get_local_filename(xfer), O_RDWR|O_CREAT, 0644); info->buffer = mmap(0, purple_xfer_get_size(xfer), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FILE, fd, 0); @@ -248,7 +248,7 @@ file_key = _gen_file_key(); bytes += qq_put8(raw_data + bytes, packet_type); - bytes += qq_put16(raw_data + bytes, QQ_CLIENT); + bytes += qq_put16(raw_data + bytes, qd->client_tag); bytes += qq_put8(raw_data + bytes, file_key & 0xff); bytes += qq_put32(raw_data + bytes, _encrypt_qq_uid(qd->uid, file_key)); bytes += qq_put32(raw_data + bytes, _encrypt_qq_uid(to_uid, file_key)); @@ -266,7 +266,7 @@ { qq_data *qd; gint bytes, bytes_expected, encrypted_len; - guint8 *raw_data, *encrypted_data; + guint8 *raw_data, *encrypted; time_t now; ft_info *info; @@ -334,19 +334,19 @@ raw_data, bytes, "sending packet[%s]:", qq_get_file_cmd_desc(packet_type)); - encrypted_data = g_newa(guint8, bytes + 16); - encrypted_len = qq_encrypt(encrypted_data, raw_data, bytes, info->file_session_key); + encrypted = g_newa(guint8, bytes + 16); + encrypted_len = qq_encrypt(encrypted, raw_data, bytes, info->file_session_key); /*debug: try to decrypt it */ #if 0 guint8 *buf; int buflen; - hex_dump = hex_dump_to_str(encrypted_data, encrypted_len); + hex_dump = hex_dump_to_str(encrypted, encrypted_len); purple_debug_info("QQ", "encrypted packet: \n%s", hex_dump); g_free(hex_dump); buf = g_newa(guint8, MAX_PACKET_SIZE); buflen = encrypted_len; - if (qq_crypt(DECRYPT, encrypted_data, encrypted_len, info->file_session_key, buf, &buflen)) { + if (qq_crypt(DECRYPT, encrypted, encrypted_len, info->file_session_key, buf, &buflen)) { purple_debug_info("QQ", "decrypt success\n"); if (buflen == bytes && memcmp(raw_data, buf, buflen) == 0) purple_debug_info("QQ", "checksum ok\n"); @@ -360,11 +360,11 @@ #endif purple_debug_info("QQ", "<== send %s packet\n", qq_get_file_cmd_desc(packet_type)); - _qq_send_file(gc, encrypted_data, encrypted_len, QQ_FILE_CONTROL_PACKET_TAG, info->to_uid); + _qq_send_file(gc, encrypted, encrypted_len, QQ_FILE_CONTROL_PACKET_TAG, info->to_uid); } /* send a file to udp channel with QQ_FILE_DATA_PACKET_TAG */ -static void _qq_send_file_data_packet(PurpleConnection *gc, guint16 packet_type, guint8 sub_type, +static void _qq_send_file_data_packet(PurpleConnection *gc, guint16 packet_type, guint8 sub_type, guint32 fragment_index, guint16 seq, guint8 *data, gint len) { guint8 *raw_data, filename_md5[QQ_KEY_LENGTH], file_md5[QQ_KEY_LENGTH]; @@ -402,11 +402,11 @@ _fill_file_md5(purple_xfer_get_local_filename(qd->xfer), purple_xfer_get_size(qd->xfer), file_md5); - + info->fragment_num = (filesize - 1) / QQ_FILE_FRAGMENT_MAXLEN + 1; info->fragment_len = QQ_FILE_FRAGMENT_MAXLEN; - purple_debug_info("QQ", + purple_debug_info("QQ", "start transfering data, %d fragments with %d length each\n", info->fragment_num, info->fragment_len); /* Unknown */ @@ -431,7 +431,7 @@ filename_len); break; case QQ_FILE_DATA_INFO: - purple_debug_info("QQ", + purple_debug_info("QQ", "sending %dth fragment with length %d, offset %d\n", fragment_index, len, (fragment_index-1)*fragment_size); /* bytes += qq_put16(raw_data + bytes, ++(qd->send_seq)); */ @@ -532,7 +532,7 @@ decryped_bytes = 0; qq_get_conn_info(info, decrypted_data + decryped_bytes); /* qq_send_file_ctl_packet(gc, QQ_FILE_CMD_PING, fh->sender_uid, 0); */ - qq_send_file_ctl_packet(gc, QQ_FILE_CMD_SENDER_SAY_HELLO, fh.sender_uid, 0); + qq_send_file_ctl_packet(gc, QQ_FILE_CMD_SENDER_SAY_HELLO, fh.sender_uid, 0); break; case QQ_FILE_CMD_SENDER_SAY_HELLO: /* I'm receiver, if we receive SAY_HELLO from sender, we send back the ACK */ @@ -573,8 +573,8 @@ ft_info *info = (ft_info *) xfer->data; guint32 mask; - purple_debug_info("QQ", - "receiving %dth fragment with length %d, slide window status %o, max_fragment_index %d\n", + purple_debug_info("QQ", + "receiving %dth fragment with length %d, slide window status %o, max_fragment_index %d\n", index, len, info->window, info->max_fragment_index); if (info->window == 0 && info->max_fragment_index == 0) { if (_qq_xfer_open_file(purple_xfer_get_local_filename(xfer), "wb", xfer) == -1) { @@ -605,7 +605,7 @@ if (mask & 0x8000) mask = 0x0001; else mask = mask << 1; } - purple_debug_info("QQ", "procceed %dth fragment, slide window status %o, max_fragment_index %d\n", + purple_debug_info("QQ", "procceed %dth fragment, slide window status %o, max_fragment_index %d\n", index, info->window, info->max_fragment_index); } @@ -650,10 +650,10 @@ PurpleXfer *xfer = qd->xfer; ft_info *info = (ft_info *) xfer->data; - purple_debug_info("QQ", - "receiving %dth fragment ack, slide window status %o, max_fragment_index %d\n", + purple_debug_info("QQ", + "receiving %dth fragment ack, slide window status %o, max_fragment_index %d\n", fragment_index, info->window, info->max_fragment_index); - if (fragment_index < info->max_fragment_index || + if (fragment_index < info->max_fragment_index || fragment_index >= info->max_fragment_index + sizeof(info->window)) { purple_debug_info("QQ", "duplicate %dth fragment, drop it!\n", fragment_index+1); return; @@ -681,7 +681,7 @@ info->window &= ~mask; buffer = g_newa(guint8, info->fragment_len); - readbytes = _qq_xfer_read_file(buffer, info->max_fragment_index + sizeof(info->window), + readbytes = _qq_xfer_read_file(buffer, info->max_fragment_index + sizeof(info->window), info->fragment_len, xfer); if (readbytes > 0) _qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP, QQ_FILE_DATA_INFO, @@ -692,8 +692,8 @@ else mask = mask << 1; } } - purple_debug_info("QQ", - "procceed %dth fragment ack, slide window status %o, max_fragment_index %d\n", + purple_debug_info("QQ", + "procceed %dth fragment ack, slide window status %o, max_fragment_index %d\n", fragment_index, info->window, info->max_fragment_index); } @@ -727,13 +727,13 @@ bytes += qq_get32(&info->fragment_num, data + bytes); bytes += qq_get32(&info->fragment_len, data + bytes); - /* FIXME: We must check the md5 here, - * if md5 doesn't match we will ignore + /* FIXME: We must check the md5 here, + * if md5 doesn't match we will ignore * the packet or send sth as error number */ info->max_fragment_index = 0; info->window = 0; - purple_debug_info("QQ", + purple_debug_info("QQ", "start receiving data, %d fragments with %d length each\n", info->fragment_num, info->fragment_len); _qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP_ACK, sub_type, @@ -743,7 +743,7 @@ bytes += qq_get32(&fragment_index, data + bytes); bytes += qq_get32(&fragment_offset, data + bytes); bytes += qq_get16(&fragment_len, data + bytes); - purple_debug_info("QQ", + purple_debug_info("QQ", "received %dth fragment with length %d, offset %d\n", fragment_index, fragment_len, fragment_offset); diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group.c --- a/libpurple/protocols/qq/group.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/group.c Wed Oct 29 21:38:53 2008 +0000 @@ -30,20 +30,19 @@ #include "group_internal.h" #include "group_info.h" -#include "group_search.h" +#include "group_join.h" #include "utils.h" #include "qq_network.h" -#include "header_info.h" -#include "group.h" +#include "qq_define.h" static void _qq_group_search_callback(PurpleConnection *gc, const gchar *input) { guint32 ext_id; g_return_if_fail(input != NULL); - ext_id = qq_string_to_dec_value(input); + ext_id = strtol(input, NULL, 10); /* 0x00000000 means search for demo group */ - qq_send_cmd_group_search_group(gc, ext_id); + qq_request_room_search(gc, ext_id, QQ_ROOM_SEARCH_ONLY); } static void _qq_group_search_cancel_callback(PurpleConnection *gc, const gchar *input) @@ -98,20 +97,8 @@ f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Group ID"), QQ_ROOM_KEY_EXTERNAL_ID, FALSE); fields = g_list_append(fields, f); - f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Creator"), QQ_ROOM_KEY_CREATOR_UID, FALSE); - fields = g_list_append(fields, f); - f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, - _("Group Description"), QQ_ROOM_KEY_DESC_UTF8, FALSE); - fields = g_list_append(fields, f); f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", QQ_ROOM_KEY_INTERNAL_ID, TRUE); fields = g_list_append(fields, f); - f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", QQ_ROOM_KEY_TYPE, TRUE); - fields = g_list_append(fields, f); - f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Auth"), QQ_ROOM_KEY_AUTH_TYPE, TRUE); - fields = g_list_append(fields, f); - f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", QQ_ROOM_KEY_CATEGORY, TRUE); - fields = g_list_append(fields, f); - f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", QQ_ROOM_KEY_TITLE_UTF8, TRUE); fields = g_list_append(fields, f); purple_roomlist_set_fields(rl, fields); @@ -142,43 +129,3 @@ purple_roomlist_set_in_progress(list, FALSE); purple_roomlist_unref(list); } - -/* this should be called upon signin, even when we did not open group chat window */ -void qq_group_init(PurpleConnection *gc) -{ - PurpleAccount *account; - PurpleChat *chat; - PurpleGroup *purple_group; - PurpleBlistNode *node; - qq_group *group; - gint count; - - account = purple_connection_get_account(gc); - - purple_group = purple_find_group(PURPLE_GROUP_QQ_QUN); - if (purple_group == NULL) { - purple_debug_info("QQ", "We have no QQ Qun\n"); - return; - } - - count = 0; - for (node = ((PurpleBlistNode *) purple_group)->child; node != NULL; node = node->next) { - if ( !PURPLE_BLIST_NODE_IS_CHAT(node)) { - continue; - } - /* got one */ - chat = (PurpleChat *) node; - if (account != chat->account) /* not qq account*/ - continue; - group = qq_room_create_by_hashtable(gc, chat->components); - if (group == NULL) - continue; - - if (group->id <= 0) - continue; - - count++; - } - - purple_debug_info("QQ", "Load %d QQ Qun configurations\n", count); -} diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group.h --- a/libpurple/protocols/qq/group.h Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/group.h Wed Oct 29 21:38:53 2008 +0000 @@ -40,10 +40,10 @@ QQ_ROOM_ROLE_ADMIN, } qq_room_role; -typedef struct _qq_group { +typedef struct _qq_room_data qq_room_data; +struct _qq_room_data { /* all these will be saved when we exit Purple */ qq_room_role my_role; /* my role for this room */ - gchar *my_role_desc; /* my role description */ guint32 id; guint32 ext_id; guint8 type8; /* permanent or temporory */ @@ -54,16 +54,14 @@ gchar *desc_utf8; /* all these will be loaded from the network */ gchar *notice_utf8; /* group notice by admin */ + + gboolean is_got_buddies; GList *members; - - gboolean is_got_info; -} qq_group; +}; GList *qq_chat_info(PurpleConnection *gc); GHashTable *qq_chat_info_defaults(PurpleConnection *gc, const gchar *chat_name); -void qq_group_init(PurpleConnection *gc); - PurpleRoomlist *qq_roomlist_get_list(PurpleConnection *gc); void qq_roomlist_cancel(PurpleRoomlist *list); diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group_conv.c --- a/libpurple/protocols/qq/group_conv.c Mon Oct 27 23:13:11 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,128 +0,0 @@ -/** - * @file group_conv.c - * - * purple - * - * Purple 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ - -#include -#include "qq.h" - -#include "group_conv.h" -#include "buddy_list.h" -#include "header_info.h" -#include "qq_network.h" -#include "qq_process.h" -#include "utils.h" - -/* show group conversation window */ -PurpleConversation *qq_room_conv_create(PurpleConnection *gc, qq_group *group) -{ - PurpleConversation *conv; - qq_data *qd; - - g_return_val_if_fail(group != NULL, NULL); - qd = (qq_data *) gc->proto_data; - - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, - group->title_utf8, purple_connection_get_account(gc)); - if (conv != NULL) { - /* show only one conversation per group */ - return conv; - } - - serv_got_joined_chat(gc, qd->channel++, group->title_utf8); - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, group->title_utf8, purple_connection_get_account(gc)); - if (conv != NULL) { - purple_conv_chat_set_topic(PURPLE_CONV_CHAT(conv), NULL, group->notice_utf8); - if (group->is_got_info) - qq_send_room_cmd_only(gc, QQ_ROOM_CMD_GET_ONLINES, group->id); - else - qq_update_room(gc, 0, group->id); - return conv; - } - return NULL; -} - -/* refresh online member in group conversation window */ -void qq_group_conv_refresh_online_member(PurpleConnection *gc, qq_group *group) -{ - GList *names, *list, *flags; - qq_buddy *member; - gchar *member_name, *member_uid; - PurpleConversation *conv; - gint flag; - g_return_if_fail(group != NULL); - - names = NULL; - flags = NULL; - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, - group->title_utf8, purple_connection_get_account(gc)); - if (conv != NULL && group->members != NULL) { - list = group->members; - while (list != NULL) { - member = (qq_buddy *) list->data; - - /* we need unique identifiers for everyone in the chat or else we'll - * run into problems with functions like get_cb_real_name from qq.c */ - member_name = (member->nickname != NULL && *(member->nickname) != '\0') ? - g_strdup_printf("%s (qq-%u)", member->nickname, member->uid) : - g_strdup_printf("(qq-%u)", member->uid); - member_uid = g_strdup_printf("(qq-%u)", member->uid); - - flag = 0; - /* TYPING to put online above OP and FOUNDER */ - if (is_online(member->status)) - flag |= (PURPLE_CBFLAGS_TYPING | PURPLE_CBFLAGS_VOICE); - if(1 == (member->role & 1)) flag |= PURPLE_CBFLAGS_OP; - if(member->uid == group->creator_uid) flag |= PURPLE_CBFLAGS_FOUNDER; - - if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(conv), member_name)) - { - purple_conv_chat_user_set_flags(PURPLE_CONV_CHAT(conv), - member_name, - flag); - } else if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(conv), member_uid)) - { - purple_conv_chat_user_set_flags(PURPLE_CONV_CHAT(conv), - member_uid, - flag); - purple_conv_chat_rename_user(PURPLE_CONV_CHAT(conv), member_uid, member_name); - } else { - /* always put it even offline */ - names = g_list_append(names, member_name); - flags = g_list_append(flags, GINT_TO_POINTER(flag)); - } - g_free(member_uid); - list = list->next; - } - - if (names != NULL && flags != NULL) { - purple_conv_chat_add_users(PURPLE_CONV_CHAT(conv), names, NULL, flags, FALSE); - } - } - /* clean up names */ - while (names != NULL) { - member_name = (gchar *) names->data; - names = g_list_remove(names, member_name); - g_free(member_name); - } - g_list_free(flags); -} diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group_conv.h --- a/libpurple/protocols/qq/group_conv.h Mon Oct 27 23:13:11 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -/** - * @file group_conv.h - * - * purple - * - * Purple 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ - -#ifndef _QQ_GROUP_CONV_H_ -#define _QQ_GROUP_CONV_H_ - -#include "connection.h" -#include "conversation.h" -#include "group.h" - -PurpleConversation *qq_room_conv_create(PurpleConnection *gc, qq_group *group); -void qq_group_conv_refresh_online_member(PurpleConnection *gc, qq_group *group); - -#endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group_find.c --- a/libpurple/protocols/qq/group_find.c Mon Oct 27 23:13:11 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,249 +0,0 @@ -/** - * @file group_find.c - * - * purple - * - * Purple 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ - -#include "qq.h" - -#include "conversation.h" -#include "debug.h" -#include "util.h" - -#include "group_find.h" -#include "utils.h" - -/* find a qq_buddy by uid, called by im.c */ -qq_buddy *qq_group_find_member_by_uid(qq_group *group, guint32 uid) -{ - GList *list; - qq_buddy *member; - g_return_val_if_fail(group != NULL && uid > 0, NULL); - - list = group->members; - while (list != NULL) { - member = (qq_buddy *) list->data; - if (member->uid == uid) - return member; - else - list = list->next; - } - - return NULL; -} - -/* remove a qq_buddy by uid, called by qq_group_opt.c */ -void qq_group_remove_member_by_uid(qq_group *group, guint32 uid) -{ - GList *list; - qq_buddy *member; - g_return_if_fail(group != NULL && uid > 0); - - list = group->members; - while (list != NULL) { - member = (qq_buddy *) list->data; - if (member->uid == uid) { - group->members = g_list_remove(group->members, member); - return; - } else { - list = list->next; - } - } -} - -qq_buddy *qq_group_find_or_add_member(PurpleConnection *gc, qq_group *group, guint32 member_uid) -{ - qq_buddy *member, *q_bud; - PurpleBuddy *buddy; - g_return_val_if_fail(group != NULL && member_uid > 0, NULL); - - member = qq_group_find_member_by_uid(group, member_uid); - if (member == NULL) { /* first appear during my session */ - member = g_new0(qq_buddy, 1); - member->uid = member_uid; - buddy = purple_find_buddy(purple_connection_get_account(gc), uid_to_purple_name(member_uid)); - if (buddy != NULL) { - q_bud = (qq_buddy *) buddy->proto_data; - if (q_bud != NULL && q_bud->nickname != NULL) - member->nickname = g_strdup(q_bud->nickname); - else if (buddy->alias != NULL) - member->nickname = g_strdup(buddy->alias); - } - group->members = g_list_append(group->members, member); - } - - return member; -} - -/* find a qq_group by chatroom channel */ -qq_group *qq_group_find_by_channel(PurpleConnection *gc, gint channel) -{ - PurpleConversation *conv; - qq_data *qd; - qq_group *group; - GList *list; - - qd = (qq_data *) gc->proto_data; - - conv = purple_find_chat(gc, channel); - g_return_val_if_fail(conv != NULL, NULL); - - list = qd->groups; - group = NULL; - while (list != NULL) { - group = (qq_group *) list->data; - if (group->title_utf8 == NULL) { - continue; - } - if (!g_ascii_strcasecmp(purple_conversation_get_name(conv), group->title_utf8)) - break; - list = list->next; - } - - return group; -} - -/* find a qq_group by its id, flag is QQ_INTERNAL_ID or QQ_EXTERNAL_ID */ -qq_group *qq_room_search_ext_id(PurpleConnection *gc, guint32 ext_id) -{ - GList *list; - qq_group *group; - qq_data *qd; - - qd = (qq_data *) gc->proto_data; - - if (qd->groups == NULL || ext_id <= 0) - return NULL; - - list = qd->groups; - while (list != NULL) { - group = (qq_group *) list->data; - if (group->ext_id == ext_id) { - return group; - } - list = list->next; - } - - return NULL; -} - -qq_group *qq_room_search_id(PurpleConnection *gc, guint32 room_id) -{ - GList *list; - qq_group *group; - qq_data *qd; - - qd = (qq_data *) gc->proto_data; - - if (qd->groups == NULL || room_id <= 0) - return NULL; - - list = qd->groups; - while (list != NULL) { - group = (qq_group *) list->data; - if (group->id == room_id) { - return group; - } - list = list->next; - } - - return NULL; -} - -qq_group *qq_room_get_next(PurpleConnection *gc, guint32 room_id) -{ - GList *list; - qq_group *group; - qq_data *qd; - gboolean is_find = FALSE; - - qd = (qq_data *) gc->proto_data; - - if (qd->groups == NULL) { - return NULL; - } - - if (room_id <= 0) { - return (qq_group *) qd->groups->data; - } - - list = qd->groups; - while (list != NULL) { - group = (qq_group *) list->data; - list = list->next; - if (group->id == room_id) { - is_find = TRUE; - break; - } - } - - if ( !is_find || list == NULL) { - return NULL; - } - - return (qq_group *)list->data; -} - -qq_group *qq_room_get_next_conv(PurpleConnection *gc, guint32 room_id) -{ - GList *list; - qq_group *group; - qq_data *qd; - gboolean is_find; - - qd = (qq_data *) gc->proto_data; - - list = qd->groups; - if (room_id > 0) { - /* search next room */ - is_find = FALSE; - while (list != NULL) { - group = (qq_group *) list->data; - list = list->next; - if (group->id == room_id) { - is_find = TRUE; - break; - } - } - if ( !is_find || list == NULL) { - return NULL; - } - } - - is_find = FALSE; - while (list != NULL) { - group = (qq_group *) list->data; - if (group->my_role == QQ_ROOM_ROLE_YES || group->my_role == QQ_ROOM_ROLE_ADMIN) { - if (NULL != purple_find_conversation_with_account( - PURPLE_CONV_TYPE_CHAT,group->title_utf8, purple_connection_get_account(gc))) { - /* In convseration*/ - is_find = TRUE; - break; - } - } - list = list->next; - } - - if ( !is_find) { - return NULL; - } - return group; -} diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group_find.h --- a/libpurple/protocols/qq/group_find.h Mon Oct 27 23:13:11 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -/** - * @file group_find.h - * - * purple - * - * Purple 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ - -#ifndef _QQ_GROUP_FIND_H_ -#define _QQ_GROUP_FIND_H_ - -#include -#include "connection.h" -#include "group.h" - -qq_buddy *qq_group_find_member_by_uid(qq_group *group, guint32 uid); -void qq_group_remove_member_by_uid(qq_group *group, guint32 uid); -qq_buddy *qq_group_find_or_add_member(PurpleConnection *gc, qq_group *group, guint32 member_uid); -qq_group *qq_group_find_by_channel(PurpleConnection *gc, gint channel); - -qq_group *qq_room_search_ext_id(PurpleConnection *gc, guint32 ext_id); -qq_group *qq_room_search_id(PurpleConnection *gc, guint32 room_id); - -qq_group *qq_room_get_next(PurpleConnection *gc, guint32 room_id); -qq_group *qq_room_get_next_conv(PurpleConnection *gc, guint32 room_id); - -#endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group_free.c --- a/libpurple/protocols/qq/group_free.c Mon Oct 27 23:13:11 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -/** - * @file group_free.c - * - * purple - * - * Purple 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ - -#include "internal.h" - -#include "debug.h" - -#include "buddy_list.h" -#include "group_free.h" - -/* gracefully free all members in a group */ -static void qq_group_free_member(qq_group *group) -{ - gint i; - GList *list; - qq_buddy *member; - - g_return_if_fail(group != NULL); - i = 0; - while (NULL != (list = group->members)) { - member = (qq_buddy *) list->data; - i++; - group->members = g_list_remove(group->members, member); - g_free(member->nickname); - g_free(member); - } - - group->members = NULL; -} - -/* gracefully free the memory for one qq_group */ -void qq_group_free(qq_group *group) -{ - g_return_if_fail(group != NULL); - qq_group_free_member(group); - g_free(group->my_role_desc); - g_free(group->title_utf8); - g_free(group->desc_utf8); - g_free(group->notice_utf8); - g_free(group); -} - -void qq_group_free_all(qq_data *qd) -{ - qq_group *group; - gint count; - - g_return_if_fail(qd != NULL); - count = 0; - while (qd->groups != NULL) { - group = (qq_group *) qd->groups->data; - qd->groups = g_list_remove(qd->groups, group); - qq_group_free(group); - count++; - } - - if (count > 0) { - purple_debug_info("QQ", "%d rooms are freed\n", count); - } -} diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group_free.h --- a/libpurple/protocols/qq/group_free.h Mon Oct 27 23:13:11 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -/** - * @file group_free.h - * - * purple - * - * Purple 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ - -#ifndef _QQ_GROUP_FREE_H_ -#define _QQ_GROUP_FREE_H_ - -#include -#include "qq.h" -#include "group.h" - -void qq_group_free(qq_group *group); -void qq_group_free_all(qq_data *qd); - -#endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group_im.c --- a/libpurple/protocols/qq/group_im.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/group_im.c Wed Oct 29 21:38:53 2008 +0000 @@ -32,43 +32,143 @@ #include "util.h" #include "char_conv.h" -#include "group_find.h" #include "group_internal.h" #include "group_info.h" +#include "group_join.h" #include "group_im.h" #include "group_opt.h" -#include "group_conv.h" #include "im.h" -#include "header_info.h" +#include "qq_define.h" #include "packet_parse.h" #include "qq_network.h" #include "qq_process.h" #include "utils.h" -typedef struct _qq_recv_group_im { - guint32 ext_id; - guint8 type8; - guint32 member_uid; - guint16 msg_seq; - time_t send_time; - guint16 msg_len; - gchar *msg; - guint8 *font_attr; - gint font_attr_len; -} qq_recv_group_im; +/* show group conversation window */ +PurpleConversation *qq_room_conv_open(PurpleConnection *gc, qq_room_data *rmd) +{ + PurpleConversation *conv; + qq_data *qd; + gchar *topic_utf8; + + g_return_val_if_fail(rmd != NULL, NULL); + qd = (qq_data *) gc->proto_data; + + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, + rmd->title_utf8, purple_connection_get_account(gc)); + if (conv != NULL) { + /* show only one conversation per room */ + return conv; + } + + serv_got_joined_chat(gc, rmd->id, rmd->title_utf8); + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, rmd->title_utf8, purple_connection_get_account(gc)); + if (conv != NULL) { + topic_utf8 = g_strdup_printf("%d %s", rmd->ext_id, rmd->notice_utf8); + purple_debug_info("QQ", "Set chat topic to %s\n", topic_utf8); + purple_conv_chat_set_topic(PURPLE_CONV_CHAT(conv), NULL, topic_utf8); + g_free(topic_utf8); + + if (rmd->is_got_buddies) + qq_send_room_cmd_only(gc, QQ_ROOM_CMD_GET_ONLINES, rmd->id); + else + qq_update_room(gc, 0, rmd->id); + return conv; + } + return NULL; +} + +/* refresh online member in group conversation window */ +void qq_room_conv_set_onlines(PurpleConnection *gc, qq_room_data *rmd) +{ + GList *names, *list, *flags; + qq_buddy_data *bd; + gchar *member_name, *member_uid; + PurpleConversation *conv; + gint flag; + gboolean is_find; + + g_return_if_fail(rmd != NULL); + + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, + rmd->title_utf8, purple_connection_get_account(gc)); + if (conv == NULL) { + purple_debug_warning("QQ", "Conversation \"%s\" is not opened\n", rmd->title_utf8); + return; + } + g_return_if_fail(rmd->members != NULL); + + names = NULL; + flags = NULL; + + list = rmd->members; + while (list != NULL) { + bd = (qq_buddy_data *) list->data; + + /* we need unique identifiers for everyone in the chat or else we'll + * run into problems with functions like get_cb_real_name from qq.c */ + member_name = (bd->nickname != NULL && *(bd->nickname) != '\0') ? + g_strdup_printf("%s (%u)", bd->nickname, bd->uid) : + g_strdup_printf("(%u)", bd->uid); + member_uid = g_strdup_printf("(%u)", bd->uid); + + flag = 0; + /* TYPING to put online above OP and FOUNDER */ + if (is_online(bd->status)) flag |= (PURPLE_CBFLAGS_TYPING | PURPLE_CBFLAGS_VOICE); + if(1 == (bd->role & 1)) flag |= PURPLE_CBFLAGS_OP; + if(bd->uid == rmd->creator_uid) flag |= PURPLE_CBFLAGS_FOUNDER; + + is_find = TRUE; + if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(conv), member_name)) + { + purple_conv_chat_user_set_flags(PURPLE_CONV_CHAT(conv), + member_name, + flag); + } else if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(conv), member_uid)) + { + purple_conv_chat_user_set_flags(PURPLE_CONV_CHAT(conv), + member_uid, + flag); + purple_conv_chat_rename_user(PURPLE_CONV_CHAT(conv), member_uid, member_name); + } else { + is_find = FALSE; + } + if (!is_find) { + /* always put it even offline */ + names = g_list_append(names, member_name); + flags = g_list_append(flags, GINT_TO_POINTER(flag)); + } else { + g_free(member_name); + } + g_free(member_uid); + list = list->next; + } + + if (names != NULL && flags != NULL) { + purple_conv_chat_add_users(PURPLE_CONV_CHAT(conv), names, NULL, flags, FALSE); + } + + /* clean up names */ + while (names != NULL) { + member_name = (gchar *) names->data; + names = g_list_remove(names, member_name); + g_free(member_name); + } + g_list_free(flags); +} /* send IM to a group */ -void qq_send_packet_group_im(PurpleConnection *gc, qq_group *group, const gchar *msg) +void qq_request_room_send_im(PurpleConnection *gc, guint32 room_id, const gchar *msg) { gint data_len, bytes; guint8 *raw_data, *send_im_tail; guint16 msg_len; gchar *msg_filtered; - g_return_if_fail(group != NULL && msg != NULL); + g_return_if_fail(room_id != 0 && msg != NULL); msg_filtered = purple_markup_strip_html(msg); - purple_debug_info("QQ_MESG", "Send qun mesg filterd: %s\n", msg_filtered); + /* purple_debug_info("QQ", "Send qun mesg filterd: %s\n", msg_filtered); */ msg_len = strlen(msg_filtered); data_len = 2 + msg_len + QQ_SEND_IM_AFTER_MSG_LEN; @@ -85,226 +185,76 @@ g_free(msg_filtered); if (bytes == data_len) /* create OK */ - qq_send_room_cmd(gc, QQ_ROOM_CMD_SEND_MSG, group->id, raw_data, data_len); + qq_send_room_cmd(gc, QQ_ROOM_CMD_SEND_MSG, room_id, raw_data, data_len); else purple_debug_error("QQ", "Fail creating group_im packet, expect %d bytes, build %d bytes\n", data_len, bytes); } /* this is the ACK */ -void qq_process_group_cmd_im(guint8 *data, gint len, PurpleConnection *gc) +void qq_process_room_send_im(PurpleConnection *gc, guint8 *data, gint len) { /* return should be the internal group id * but we have nothing to do with it */ return; } -/* receive an application to join the group */ -void qq_process_room_msg_apply_join(guint8 *data, gint len, guint32 id, PurpleConnection *gc) +void qq_room_got_chat_in(PurpleConnection *gc, + guint32 room_id, guint32 uid_from, const gchar *msg, time_t in_time) { - guint32 ext_id, user_uid; - guint8 type8; - gchar *reason_utf8, *msg, *reason; - group_member_opt *g; - gchar *nombre; - gint bytes = 0; - - g_return_if_fail(id > 0 && data != NULL && len > 0); - - /* FIXME: check length here */ - - bytes += qq_get32(&ext_id, data + bytes); - bytes += qq_get8(&type8, data + bytes); - bytes += qq_get32(&user_uid, data + bytes); - - g_return_if_fail(ext_id > 0 && user_uid > 0); - - bytes += convert_as_pascal_string(data + bytes, &reason_utf8, QQ_CHARSET_DEFAULT); - - msg = g_strdup_printf(_("%d request to join Qun %d"), user_uid, ext_id); - reason = g_strdup_printf(_("Message: %s"), reason_utf8); - - g = g_new0(group_member_opt, 1); - g->gc = gc; - g->id = id; - g->member = user_uid; - - nombre = uid_to_purple_name(user_uid); + PurpleConversation *conv; + qq_buddy_data *bd; + qq_room_data *rmd; + gchar *from; - purple_request_action(gc, _("QQ Qun Operation"), - msg, reason, - PURPLE_DEFAULT_ACTION_NONE, - purple_connection_get_account(gc), nombre, NULL, - g, 3, - _("Approve"), - G_CALLBACK - (qq_group_approve_application_with_struct), - _("Reject"), - G_CALLBACK - (qq_group_reject_application_with_struct), - _("Search"), G_CALLBACK(qq_group_search_application_with_struct)); - - g_free(nombre); - g_free(reason); - g_free(msg); - g_free(reason_utf8); -} + g_return_if_fail(gc != NULL && room_id != 0); -/* the request to join a group is rejected */ -void qq_process_room_msg_been_rejected(guint8 *data, gint len, guint32 id, PurpleConnection *gc) -{ - guint32 ext_id, admin_uid; - guint8 type8; - gchar *reason_utf8, *msg, *reason; - qq_group *group; - gint bytes = 0; - - g_return_if_fail(data != NULL && len > 0); - - /* FIXME: check length here */ + conv = purple_find_chat(gc, room_id); + rmd = qq_room_data_find(gc, room_id); + g_return_if_fail(rmd != NULL); - bytes += qq_get32(&ext_id, data + bytes); - bytes += qq_get8(&type8, data + bytes); - bytes += qq_get32(&admin_uid, data + bytes); - - g_return_if_fail(ext_id > 0 && admin_uid > 0); - - bytes += convert_as_pascal_string(data + bytes, &reason_utf8, QQ_CHARSET_DEFAULT); - - msg = g_strdup_printf - (_("Failed to join Qun %d, operated by admin %d"), ext_id, admin_uid); - reason = g_strdup_printf(_("Message: %s"), reason_utf8); - - purple_notify_warning(gc, _("QQ Qun Operation"), msg, reason); - - group = qq_room_search_id(gc, id); - if (group != NULL) { - group->my_role = QQ_ROOM_ROLE_NO; - qq_group_refresh(gc, group); + if (conv == NULL && purple_prefs_get_bool("/plugins/prpl/qq/auto_popup_conversation")) { + conv = qq_room_conv_open(gc, rmd); } - g_free(reason); - g_free(msg); - g_free(reason_utf8); -} - -/* the request to join a group is approved */ -void qq_process_room_msg_been_approved(guint8 *data, gint len, guint32 id, PurpleConnection *gc) -{ - guint32 ext_id, admin_uid; - guint8 type8; - gchar *reason_utf8, *msg; - qq_group *group; - gint bytes = 0; - - g_return_if_fail(data != NULL && len > 0); - - /* FIXME: check length here */ - - bytes += qq_get32(&ext_id, data + bytes); - bytes += qq_get8(&type8, data + bytes); - bytes += qq_get32(&admin_uid, data + bytes); - - g_return_if_fail(ext_id > 0 && admin_uid > 0); - /* it is also a "无" here, so do not display */ - bytes += convert_as_pascal_string(data + bytes, &reason_utf8, QQ_CHARSET_DEFAULT); - - msg = g_strdup_printf - (_("Successed to join Qun %d, operated by admin %d"), ext_id, admin_uid); - - purple_notify_warning(gc, _("QQ Qun Operation"), msg, NULL); - - group = qq_room_search_id(gc, id); - if (group != NULL) { - group->my_role = QQ_ROOM_ROLE_YES; - qq_group_refresh(gc, group); + if (conv == NULL) { + return; } - g_free(msg); - g_free(reason_utf8); -} - -/* process the packet when removed from a group */ -void qq_process_room_msg_been_removed(guint8 *data, gint len, guint32 id, PurpleConnection *gc) -{ - guint32 ext_id, uid; - guint8 type8; - gchar *msg; - qq_group *group; - gint bytes = 0; - - g_return_if_fail(data != NULL && len > 0); - - /* FIXME: check length here */ - - bytes += qq_get32(&ext_id, data + bytes); - bytes += qq_get8(&type8, data + bytes); - bytes += qq_get32(&uid, data + bytes); - - g_return_if_fail(ext_id > 0 && uid > 0); - - msg = g_strdup_printf(_("[%d] removed from Qun \"%d\""), uid, ext_id); - purple_notify_info(gc, _("QQ Qun Operation"), _("Notice:"), msg); - - group = qq_room_search_id(gc, id); - if (group != NULL) { - group->my_role = QQ_ROOM_ROLE_NO; - qq_group_refresh(gc, group); - } - - g_free(msg); -} + if (uid_from != 0) { -/* process the packet when added to a group */ -void qq_process_room_msg_been_added(guint8 *data, gint len, guint32 id, PurpleConnection *gc) -{ - guint32 ext_id, uid; - guint8 type8; - qq_group *group; - gchar *msg; - gint bytes = 0; - - g_return_if_fail(data != NULL && len > 0); - - /* FIXME: check length here */ - - bytes += qq_get32(&ext_id, data + bytes); - bytes += qq_get8(&type8, data + bytes); - bytes += qq_get32(&uid, data + bytes); - - g_return_if_fail(ext_id > 0 && uid > 0); - - msg = g_strdup_printf(_("[%d] added to Qun \"%d\""), uid, ext_id); - purple_notify_info(gc, _("QQ Qun Operation"), _("Notice:"), msg); - - group = qq_room_search_id(gc, id); - if (group != NULL) { - group->my_role = QQ_ROOM_ROLE_YES; - qq_group_refresh(gc, group); - } else { /* no such group, try to create a dummy first, and then update */ - group = qq_group_create_internal_record(gc, id, ext_id, NULL); - group->my_role = QQ_ROOM_ROLE_YES; - qq_group_refresh(gc, group); - qq_update_room(gc, 0, group->id); - /* the return of this cmd will automatically update the group in blist */ + bd = qq_room_buddy_find(rmd, uid_from); + if (bd == NULL || bd->nickname == NULL) + from = g_strdup_printf("%d", uid_from); + else + from = g_strdup(bd->nickname); + } else { + from = g_strdup(""); } - - g_free(msg); + serv_got_chat_in(gc, room_id, from, 0, msg, in_time); + g_free(from); } /* recv an IM from a group chat */ -void qq_process_room_msg_normal(guint8 *data, gint data_len, guint32 id, PurpleConnection *gc, guint16 im_type) +void qq_process_room_im(guint8 *data, gint data_len, guint32 id, PurpleConnection *gc, guint16 msg_type) { - gchar *msg_with_purple_smiley, *msg_utf8_encoded, *im_src_name; - guint16 unknown; - guint32 unknown4; - PurpleConversation *conv; + gchar *msg_with_purple_smiley, *msg_utf8_encoded; qq_data *qd; - qq_buddy *member; - qq_group *group; - qq_recv_group_im *im_group; gint skip_len; - gint bytes = 0; + gint bytes ; + struct { + guint32 ext_id; + guint8 type8; + guint32 member_uid; + guint16 unknown; + guint16 msg_seq; + time_t send_time; + guint32 unknown4; + guint16 msg_len; + gchar *msg; + guint8 *font_attr; + gint font_attr_len; + } packet; g_return_if_fail(data != NULL && data_len > 0); @@ -312,24 +262,22 @@ qd = (qq_data *) gc->proto_data; -#if 0 - qq_hex_dump(PURPLE_DEBUG_INFO, "QQ", data, data_len, "group im hex dump"); -#endif + /* qq_show_packet("ROOM_IM", data, data_len); */ - im_group = g_newa(qq_recv_group_im, 1); + memset(&packet, 0, sizeof(packet)); + bytes = 0; + bytes += qq_get32(&(packet.ext_id), data + bytes); + bytes += qq_get8(&(packet.type8), data + bytes); - bytes += qq_get32(&(im_group->ext_id), data + bytes); - bytes += qq_get8(&(im_group->type8), data + bytes); - - if(QQ_RECV_IM_TEMP_QUN_IM == im_type) { + if(QQ_MSG_TEMP_QUN_IM == msg_type) { bytes += qq_get32(&(id), data + bytes); } - bytes += qq_get32(&(im_group->member_uid), bytes + data); - bytes += qq_get16(&unknown, data + bytes); /* 0x0001? */ - bytes += qq_get16(&(im_group->msg_seq), data + bytes); - bytes += qq_getime(&im_group->send_time, data + bytes); - bytes += qq_get32(&unknown4, data + bytes); /* versionID */ + bytes += qq_get32(&(packet.member_uid), bytes + data); + bytes += qq_get16(&packet.unknown, data + bytes); /* 0x0001? */ + bytes += qq_get16(&(packet.msg_seq), data + bytes); + bytes += qq_getime(&packet.send_time, data + bytes); + bytes += qq_get32(&packet.unknown4, data + bytes); /* versionID */ /* * length includes font_attr * this msg_len includes msg and font_attr @@ -340,9 +288,8 @@ * 3. font_attr */ - bytes += qq_get16(&(im_group->msg_len), data + bytes); - g_return_if_fail(im_group->msg_len > 0); - + bytes += qq_get16(&(packet.msg_len), data + bytes); + g_return_if_fail(packet.msg_len > 0); /* * 10 bytes from lumaqq * contentType = buf.getChar(); @@ -352,50 +299,37 @@ * buf.getInt(); */ - if(im_type != QQ_RECV_IM_UNKNOWN_QUN_IM) + if(msg_type != QQ_MSG_UNKNOWN_QUN_IM) skip_len = 10; else skip_len = 0; bytes += skip_len; - im_group->msg = g_strdup((gchar *) data + bytes); - bytes += strlen(im_group->msg) + 1; + /* qq_show_packet("Message", data + bytes, data_len - bytes); */ + + packet.msg = g_strdup((gchar *) data + bytes); + bytes += strlen(packet.msg) + 1; /* there might not be any font_attr, check it */ - im_group->font_attr_len = im_group->msg_len - strlen(im_group->msg) - 1 - skip_len; - if (im_group->font_attr_len > 0) - im_group->font_attr = g_memdup(data + bytes, im_group->font_attr_len); - else - im_group->font_attr = NULL; + packet.font_attr_len = data_len - bytes; + if (packet.font_attr_len > 0) { + packet.font_attr = g_memdup(data + bytes, packet.font_attr_len); + /* qq_show_packet("font_attr", packet.font_attr, packet.font_attr_len); */ + } else { + packet.font_attr = NULL; + } /* group im_group has no flag to indicate whether it has font_attr or not */ - msg_with_purple_smiley = qq_smiley_to_purple(im_group->msg); - if (im_group->font_attr_len > 0) - msg_utf8_encoded = qq_encode_to_purple(im_group->font_attr, - im_group->font_attr_len, msg_with_purple_smiley); - else + msg_with_purple_smiley = qq_smiley_to_purple(packet.msg); + if (packet.font_attr_len > 0) { + msg_utf8_encoded = qq_encode_to_purple(packet.font_attr, + packet.font_attr_len, msg_with_purple_smiley, qd->client_version); + } else { msg_utf8_encoded = qq_to_utf8(msg_with_purple_smiley, QQ_CHARSET_DEFAULT); - - group = qq_room_search_id(gc, id); - g_return_if_fail(group != NULL); + } + qq_room_got_chat_in(gc, id, packet.member_uid, msg_utf8_encoded, packet.send_time); - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, group->title_utf8, purple_connection_get_account(gc)); - if (conv == NULL && purple_prefs_get_bool("/plugins/prpl/qq/show_room_when_newin")) { - conv = qq_room_conv_create(gc, group); - } - - if (conv != NULL) { - member = qq_group_find_member_by_uid(group, im_group->member_uid); - if (member == NULL || member->nickname == NULL) - im_src_name = uid_to_purple_name(im_group->member_uid); - else - im_src_name = g_strdup(member->nickname); - serv_got_chat_in(gc, - purple_conv_chat_get_id(PURPLE_CONV_CHAT - (conv)), im_src_name, 0, msg_utf8_encoded, im_group->send_time); - g_free(im_src_name); - } g_free(msg_with_purple_smiley); g_free(msg_utf8_encoded); - g_free(im_group->msg); - g_free(im_group->font_attr); + g_free(packet.msg); + g_free(packet.font_attr); } diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group_im.h --- a/libpurple/protocols/qq/group_im.h Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/group_im.h Wed Oct 29 21:38:53 2008 +0000 @@ -27,21 +27,18 @@ #include #include "connection.h" +#include "conversation.h" #include "group.h" -void qq_send_packet_group_im(PurpleConnection *gc, qq_group *group, const gchar *msg); - -void qq_process_group_cmd_im(guint8 *data, gint len, PurpleConnection *gc); +PurpleConversation *qq_room_conv_open(PurpleConnection *gc, qq_room_data *rmd); +void qq_room_conv_set_onlines(PurpleConnection *gc, qq_room_data *rmd); -void qq_process_room_msg_normal(guint8 *data, gint data_len, guint32 id, PurpleConnection *gc, guint16 im_type); - -void qq_process_room_msg_apply_join(guint8 *data, gint len, guint32 id, PurpleConnection *gc); +void qq_room_got_chat_in(PurpleConnection *gc, + guint32 room_id, guint32 uid_from, const gchar *msg, time_t in_time); -void qq_process_room_msg_been_rejected(guint8 *data, gint len, guint32 id, PurpleConnection *gc); - -void qq_process_room_msg_been_approved(guint8 *data, gint len, guint32 id, PurpleConnection *gc); +void qq_request_room_send_im(PurpleConnection *gc, guint32 room_id, const gchar *msg); +void qq_process_room_send_im(PurpleConnection *gc, guint8 *data, gint len); -void qq_process_room_msg_been_removed(guint8 *data, gint len, guint32 id, PurpleConnection *gc); +void qq_process_room_im(guint8 *data, gint data_len, guint32 id, PurpleConnection *gc, guint16 msg_type); -void qq_process_room_msg_been_added(guint8 *data, gint len, guint32 id, PurpleConnection *gc); #endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group_info.c --- a/libpurple/protocols/qq/group_info.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/group_info.c Wed Oct 29 21:38:53 2008 +0000 @@ -28,11 +28,11 @@ #include "debug.h" #include "char_conv.h" -#include "group_find.h" +#include "group_im.h" #include "group_internal.h" #include "group_info.h" #include "buddy_list.h" -#include "header_info.h" +#include "qq_define.h" #include "packet_parse.h" #include "qq_network.h" #include "utils.h" @@ -41,7 +41,7 @@ * this interval determines if their member info is outdated */ #define QQ_GROUP_CHAT_REFRESH_NICKNAME_INTERNAL 180 -static gboolean check_update_interval(qq_buddy *member) +static gboolean check_update_interval(qq_buddy_data *member) { g_return_val_if_fail(member != NULL, FALSE); return (member->nickname == NULL) || @@ -50,32 +50,37 @@ /* this is done when we receive the reply to get_online_members sub_cmd * all member are set offline, and then only those in reply packets are online */ -static void set_all_offline(qq_group *group) +static void set_all_offline(qq_room_data *rmd) { GList *list; - qq_buddy *member; - g_return_if_fail(group != NULL); + qq_buddy_data *bd; + g_return_if_fail(rmd != NULL); - list = group->members; + list = rmd->members; while (list != NULL) { - member = (qq_buddy *) list->data; - member->status = QQ_BUDDY_CHANGE_TO_OFFLINE; + bd = (qq_buddy_data *) list->data; + bd->status = QQ_BUDDY_CHANGE_TO_OFFLINE; list = list->next; } } /* send packet to get info for each group member */ -gint qq_request_room_get_buddies(PurpleConnection *gc, qq_group *group, gint update_class) +gint qq_request_room_get_buddies(PurpleConnection *gc, guint32 room_id, gint update_class) { guint8 *raw_data; gint bytes, num; GList *list; - qq_buddy *member; + qq_room_data *rmd; + qq_buddy_data *bd; + + g_return_val_if_fail(room_id > 0, 0); - g_return_val_if_fail(group != NULL, 0); - for (num = 0, list = group->members; list != NULL; list = list->next) { - member = (qq_buddy *) list->data; - if (check_update_interval(member)) + rmd = qq_room_data_find(gc, room_id); + g_return_val_if_fail(rmd != NULL, 0); + + for (num = 0, list = rmd->members; list != NULL; list = list->next) { + bd = (qq_buddy_data *) list->data; + if (check_update_interval(bd)) num++; } @@ -88,37 +93,101 @@ bytes = 0; - list = group->members; + list = rmd->members; while (list != NULL) { - member = (qq_buddy *) list->data; - if (check_update_interval(member)) - bytes += qq_put32(raw_data + bytes, member->uid); + bd = (qq_buddy_data *) list->data; + if (check_update_interval(bd)) + bytes += qq_put32(raw_data + bytes, bd->uid); list = list->next; } - qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_BUDDIES, group->id, raw_data, bytes, + qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_BUDDIES, rmd->id, raw_data, bytes, update_class, 0); return num; } -void qq_process_room_cmd_get_info(guint8 *data, gint data_len, PurpleConnection *gc) +static gchar *get_role_desc(qq_room_role role) +{ + const char *role_desc; + switch (role) { + case QQ_ROOM_ROLE_NO: + role_desc = _("Not member"); + break; + case QQ_ROOM_ROLE_YES: + role_desc = _("Member"); + break; + case QQ_ROOM_ROLE_REQUESTING: + role_desc = _("Requesting"); + break; + case QQ_ROOM_ROLE_ADMIN: + role_desc = _("Admin"); + break; + default: + role_desc = _("Unknown"); + } + + return g_strdup(role_desc); +} + +static void room_info_display(PurpleConnection *gc, qq_room_data *rmd) { - qq_group *group; - qq_buddy *member; + PurpleNotifyUserInfo *room_info; + gchar *utf8_value; + + g_return_if_fail(rmd != NULL && rmd->id > 0); + + room_info = purple_notify_user_info_new(); + + purple_notify_user_info_add_pair(room_info, _("Title"), rmd->title_utf8); + purple_notify_user_info_add_pair(room_info, _("Notice"), rmd->notice_utf8); + purple_notify_user_info_add_pair(room_info, _("Detail"), rmd->desc_utf8); + + purple_notify_user_info_add_section_break(room_info); + + utf8_value = g_strdup_printf(("%d"), rmd->creator_uid); + purple_notify_user_info_add_pair(room_info, _("Creator"), utf8_value); + g_free(utf8_value); + + utf8_value = get_role_desc(rmd->my_role); + purple_notify_user_info_add_pair(room_info, _("About me"), utf8_value); + g_free(utf8_value); + + utf8_value = g_strdup_printf(("%d"), rmd->category); + purple_notify_user_info_add_pair(room_info, _("Category"), utf8_value); + g_free(utf8_value); + + utf8_value = g_strdup_printf(("%d"), rmd->auth_type); + purple_notify_user_info_add_pair(room_info, _("Authorize"), utf8_value); + g_free(utf8_value); + + utf8_value = g_strdup_printf(("%d"), rmd->ext_id); + purple_notify_userinfo(gc, utf8_value, room_info, NULL, NULL); + g_free(utf8_value); + + purple_notify_user_info_destroy(room_info); +} + +void qq_process_room_cmd_get_info(guint8 *data, gint data_len, guint32 action, PurpleConnection *gc) +{ qq_data *qd; - PurpleConversation *purple_conv; + qq_room_data *rmd; + qq_buddy_data *bd; + PurpleChat *chat; + PurpleConversation *conv; guint8 organization, role; guint16 unknown, max_members; guint32 member_uid, id, ext_id; - GSList *pending_id; guint32 unknown4; guint8 unknown1; gint bytes, num; gchar *notice; + gchar *topic_utf8; g_return_if_fail(data != NULL && data_len > 0); qd = (qq_data *) gc->proto_data; + /* qq_show_packet("Room Info", data, data_len); */ + bytes = 0; bytes += qq_get32(&id, data + bytes); g_return_if_fail(id > 0); @@ -126,22 +195,18 @@ bytes += qq_get32(&ext_id, data + bytes); g_return_if_fail(ext_id > 0); - pending_id = qq_get_pending_id(qd->adding_groups_from_server, id); - if (pending_id != NULL) { - qq_set_pending_id(&qd->adding_groups_from_server, id, FALSE); - qq_group_create_internal_record(gc, id, ext_id, NULL); - } + chat = qq_room_find_or_new(gc, id, ext_id); + g_return_if_fail(chat != NULL); + rmd = qq_room_data_find(gc, id); + g_return_if_fail(rmd != NULL); - group = qq_room_search_id(gc, id); - g_return_if_fail(group != NULL); - - bytes += qq_get8(&(group->type8), data + bytes); + bytes += qq_get8(&(rmd->type8), data + bytes); bytes += qq_get32(&unknown4, data + bytes); /* unknown 4 bytes */ - bytes += qq_get32(&(group->creator_uid), data + bytes); - bytes += qq_get8(&(group->auth_type), data + bytes); + bytes += qq_get32(&(rmd->creator_uid), data + bytes); + bytes += qq_get8(&(rmd->auth_type), data + bytes); bytes += qq_get32(&unknown4, data + bytes); /* oldCategory */ bytes += qq_get16(&unknown, data + bytes); - bytes += qq_get32(&(group->category), data + bytes); + bytes += qq_get32(&(rmd->category), data + bytes); bytes += qq_get16(&max_members, data + bytes); bytes += qq_get8(&unknown1, data + bytes); /* the following, while Eva: @@ -150,16 +215,21 @@ * qunDestLen(qunDestcontent)) */ bytes += qq_get8(&unknown1, data + bytes); purple_debug_info("QQ", "type=%u creatorid=%u category=%u maxmembers=%u\n", - group->type8, group->creator_uid, group->category, max_members); + rmd->type8, rmd->creator_uid, rmd->category, max_members); + if (qd->client_version >= 2007) { + /* skip 7 bytes unknow in qq2007 0x(00 00 01 00 00 00 fc)*/ + bytes += 7; + } + /* qq_show_packet("Room Info", data + bytes, data_len - bytes); */ /* strlen + */ - bytes += convert_as_pascal_string(data + bytes, &(group->title_utf8), QQ_CHARSET_DEFAULT); + bytes += qq_get_vstr(&(rmd->title_utf8), QQ_CHARSET_DEFAULT, data + bytes); bytes += qq_get16(&unknown, data + bytes); /* 0x0000 */ - bytes += convert_as_pascal_string(data + bytes, ¬ice, QQ_CHARSET_DEFAULT); - bytes += convert_as_pascal_string(data + bytes, &(group->desc_utf8), QQ_CHARSET_DEFAULT); + bytes += qq_get_vstr(¬ice, QQ_CHARSET_DEFAULT, data + bytes); + bytes += qq_get_vstr(&(rmd->desc_utf8), QQ_CHARSET_DEFAULT, data + bytes); purple_debug_info("QQ", "room [%s] notice [%s] desc [%s] unknow 0x%04X\n", - group->title_utf8, notice, group->desc_utf8, unknown); + rmd->title_utf8, notice, rmd->desc_utf8, unknown); num = 0; /* now comes the member list separated by 0x00 */ @@ -175,37 +245,42 @@ } #endif - member = qq_group_find_or_add_member(gc, group, member_uid); - if (member != NULL) - member->role = role; + bd = qq_room_buddy_find_or_new(gc, rmd, member_uid); + if (bd != NULL) + bd->role = role; } if(bytes > data_len) { purple_debug_error("QQ", "group_cmd_get_group_info: Dangerous error! maybe protocol changed, notify me!"); } - purple_debug_info("QQ", "group \"%s\" has %d members\n", group->title_utf8, num); + purple_debug_info("QQ", "group \"%s\" has %d members\n", rmd->title_utf8, num); - if (group->creator_uid == qd->uid) - group->my_role = QQ_ROOM_ROLE_ADMIN; + if (rmd->creator_uid == qd->uid) + rmd->my_role = QQ_ROOM_ROLE_ADMIN; /* filter \r\n in notice */ qq_filter_str(notice); - group->notice_utf8 = strdup(notice); + rmd->notice_utf8 = strdup(notice); g_free(notice); - qq_group_refresh(gc, group); + qq_room_update_chat_info(chat, rmd); - purple_conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, - group->title_utf8, purple_connection_get_account(gc)); - if(NULL == purple_conv) { - purple_debug_warning("QQ", - "Conversation \"%s\" is not open, do not set topic\n", group->title_utf8); + if (action == QQ_ROOM_INFO_DISPLAY) { + room_info_display(gc, rmd); + } + + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, + rmd->title_utf8, purple_connection_get_account(gc)); + if(NULL == conv) { + purple_debug_warning("QQ", "Conversation \"%s\" is not opened\n", rmd->title_utf8); return; } - purple_debug_info("QQ", "Set chat topic to %s\n", group->notice_utf8); - purple_conv_chat_set_topic(PURPLE_CONV_CHAT(purple_conv), NULL, group->notice_utf8); + topic_utf8 = g_strdup_printf("%d %s", rmd->ext_id, rmd->notice_utf8); + purple_debug_info("QQ", "Set chat topic to %s\n", topic_utf8); + purple_conv_chat_set_topic(PURPLE_CONV_CHAT(conv), NULL, topic_utf8); + g_free(topic_utf8); } void qq_process_room_cmd_get_onlines(guint8 *data, gint len, PurpleConnection *gc) @@ -213,8 +288,8 @@ guint32 id, member_uid; guint8 unknown; gint bytes, num; - qq_group *group; - qq_buddy *member; + qq_room_data *rmd; + qq_buddy_data *bd; g_return_if_fail(data != NULL && len > 0); @@ -228,28 +303,29 @@ bytes += qq_get8(&unknown, data + bytes); /* 0x3c ?? */ g_return_if_fail(id > 0); - group = qq_room_search_id(gc, id); - if (group == NULL) { + rmd = qq_room_data_find(gc, id); + if (rmd == NULL) { purple_debug_error("QQ", "We have no group info for internal id [%d]\n", id); return; } /* set all offline first, then update those online */ - set_all_offline(group); + set_all_offline(rmd); num = 0; while (bytes < len) { bytes += qq_get32(&member_uid, data + bytes); num++; - member = qq_group_find_or_add_member(gc, group, member_uid); - if (member != NULL) - member->status = QQ_BUDDY_ONLINE_NORMAL; + bd = qq_room_buddy_find_or_new(gc, rmd, member_uid); + if (bd != NULL) + bd->status = QQ_BUDDY_ONLINE_NORMAL; } if(bytes > len) { purple_debug_error("QQ", "group_cmd_get_online_members: Dangerous error! maybe protocol changed, notify developers!"); } - purple_debug_info("QQ", "Group \"%s\" has %d online members\n", group->title_utf8, num); + purple_debug_info("QQ", "Group \"%s\" has %d online members\n", rmd->title_utf8, num); + qq_room_conv_set_onlines(gc, rmd); } /* process the reply to get_members_info packet */ @@ -259,57 +335,58 @@ gint num; guint32 id, member_uid; guint16 unknown; - qq_group *group; - qq_buddy *member; + qq_room_data *rmd; + qq_buddy_data *bd; gchar *nick; g_return_if_fail(data != NULL && len > 0); -#if 0 - qq_show_packet("qq_process_room_cmd_get_buddies", data, len); -#endif + /* qq_show_packet("qq_process_room_cmd_get_buddies", data, len); */ bytes = 0; bytes += qq_get32(&id, data + bytes); g_return_if_fail(id > 0); - group = qq_room_search_id(gc, id); - g_return_if_fail(group != NULL); + rmd = qq_room_data_find(gc, id); + g_return_if_fail(rmd != NULL); num = 0; /* now starts the member info, as get buddy list reply */ while (bytes < len) { bytes += qq_get32(&member_uid, data + bytes); g_return_if_fail(member_uid > 0); - member = qq_group_find_member_by_uid(group, member_uid); - g_return_if_fail(member != NULL); + bd = qq_room_buddy_find_or_new(gc, rmd, member_uid); + g_return_if_fail(bd != NULL); num++; - bytes += qq_get16(&(member->face), data + bytes); - bytes += qq_get8(&(member->age), data + bytes); - bytes += qq_get8(&(member->gender), data + bytes); - bytes += convert_as_pascal_string(data + bytes, &nick, QQ_CHARSET_DEFAULT); + bytes += qq_get16(&(bd->face), data + bytes); + bytes += qq_get8(&(bd->age), data + bytes); + bytes += qq_get8(&(bd->gender), data + bytes); + bytes += qq_get_vstr(&nick, QQ_CHARSET_DEFAULT, data + bytes); bytes += qq_get16(&unknown, data + bytes); - bytes += qq_get8(&(member->ext_flag), data + bytes); - bytes += qq_get8(&(member->comm_flag), data + bytes); + bytes += qq_get8(&(bd->ext_flag), data + bytes); + bytes += qq_get8(&(bd->comm_flag), data + bytes); /* filter \r\n in nick */ qq_filter_str(nick); - member->nickname = g_strdup(nick); + bd->nickname = g_strdup(nick); g_free(nick); #if 0 purple_debug_info("QQ", "member [%09d]: ext_flag=0x%02x, comm_flag=0x%02x, nick=%s\n", - member_uid, member->ext_flag, member->comm_flag, member->nickname); + member_uid, bd->ext_flag, bd->comm_flag, bd->nickname); #endif - member->last_update = time(NULL); + bd->last_update = time(NULL); } if (bytes > len) { purple_debug_error("QQ", "group_cmd_get_members_info: Dangerous error! maybe protocol changed, notify developers!"); } - purple_debug_info("QQ", "Group \"%s\" obtained %d member info\n", group->title_utf8, num); + purple_debug_info("QQ", "Group \"%s\" obtained %d member info\n", rmd->title_utf8, num); + + rmd->is_got_buddies = TRUE; + qq_room_conv_set_onlines(gc, rmd); } diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group_info.h --- a/libpurple/protocols/qq/group_info.h Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/group_info.h Wed Oct 29 21:38:53 2008 +0000 @@ -29,9 +29,14 @@ #include "connection.h" #include "group.h" -gint qq_request_room_get_buddies(PurpleConnection *gc, qq_group *group, gint update_class); +enum { + QQ_ROOM_INFO_UPDATE_ONLY = 0, + QQ_ROOM_INFO_DISPLAY, +}; -void qq_process_room_cmd_get_info(guint8 *data, gint len, PurpleConnection *gc); +gint qq_request_room_get_buddies(PurpleConnection *gc, guint32 room_id, gint update_class); + +void qq_process_room_cmd_get_info(guint8 *data, gint len, guint32 action, PurpleConnection *gc); void qq_process_room_cmd_get_onlines(guint8 *data, gint len, PurpleConnection *gc); void qq_process_room_cmd_get_buddies(guint8 *data, gint len, PurpleConnection *gc); #endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group_internal.c --- a/libpurple/protocols/qq/group_internal.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/group_internal.c Wed Oct 29 21:38:53 2008 +0000 @@ -27,216 +27,396 @@ #include "debug.h" #include "buddy_opt.h" -#include "group_free.h" #include "group_internal.h" #include "utils.h" -static gchar *get_role_desc(qq_group *group) +static qq_room_data *room_data_new(guint32 id, guint32 ext_id, gchar *title) { - const char *role_desc; - g_return_val_if_fail(group != NULL, g_strdup("")); + qq_room_data *rmd; + + purple_debug_info("QQ", "Created room data: %s, ext id %d, id %d\n", + title, ext_id, id); + rmd = g_new0(qq_room_data, 1); + rmd->my_role = QQ_ROOM_ROLE_NO; + rmd->id = id; + rmd->ext_id = ext_id; + rmd->type8 = 0x01; /* assume permanent Qun */ + rmd->creator_uid = 10000; /* assume by QQ admin */ + rmd->category = 0x01; + rmd->auth_type = 0x02; /* assume need auth */ + rmd->title_utf8 = g_strdup(title == NULL ? "" : title); + rmd->desc_utf8 = g_strdup(""); + rmd->notice_utf8 = g_strdup(""); + rmd->members = NULL; + rmd->is_got_buddies = FALSE; + return rmd; +} - switch (group->my_role) { - case QQ_ROOM_ROLE_NO: - role_desc = _("I am not a member"); - break; - case QQ_ROOM_ROLE_YES: - role_desc = _("I am a member"); - break; - case QQ_ROOM_ROLE_REQUESTING: - role_desc = _("I am requesting"); - break; - case QQ_ROOM_ROLE_ADMIN: - role_desc = _("I am the admin"); - break; - default: - role_desc = _("Unknown status"); +/* create a qq_room_data from hashtable */ +static qq_room_data *room_data_new_by_hashtable(PurpleConnection *gc, GHashTable *data) +{ + qq_room_data *rmd; + guint32 id, ext_id; + gchar *value; + + value = g_hash_table_lookup(data, QQ_ROOM_KEY_INTERNAL_ID); + id = value ? strtol(value, NULL, 10) : 0; + value= g_hash_table_lookup(data, QQ_ROOM_KEY_EXTERNAL_ID); + ext_id = value ? strtol(value, NULL, 10) : 0; + value = g_strdup(g_hash_table_lookup(data, QQ_ROOM_KEY_TITLE_UTF8)); + + rmd = room_data_new(id, ext_id, value); + rmd->my_role = QQ_ROOM_ROLE_YES; + return rmd; +} + +/* gracefully free all members in a room */ +static void room_buddies_free(qq_room_data *rmd) +{ + gint i; + GList *list; + qq_buddy_data *bd; + + g_return_if_fail(rmd != NULL); + i = 0; + while (NULL != (list = rmd->members)) { + bd = (qq_buddy_data *) list->data; + i++; + rmd->members = g_list_remove(rmd->members, bd); + qq_buddy_data_free(bd); } - return g_strdup(role_desc); + rmd->members = NULL; +} + +/* gracefully free the memory for one qq_room_data */ +static void room_data_free(qq_room_data *rmd) +{ + g_return_if_fail(rmd != NULL); + room_buddies_free(rmd); + g_free(rmd->title_utf8); + g_free(rmd->desc_utf8); + g_free(rmd->notice_utf8); + g_free(rmd); } -static void add_room_to_blist(PurpleConnection *gc, qq_group *group) +void qq_room_update_chat_info(PurpleChat *chat, qq_room_data *rmd) +{ + if (rmd->title_utf8 != NULL && strlen(rmd->title_utf8) > 0) { + purple_blist_alias_chat(chat, rmd->title_utf8); + } + g_hash_table_replace(chat->components, + g_strdup(QQ_ROOM_KEY_INTERNAL_ID), + g_strdup_printf("%d", rmd->id)); + g_hash_table_replace(chat->components, + g_strdup(QQ_ROOM_KEY_EXTERNAL_ID), + g_strdup_printf("%d", rmd->ext_id)); + g_hash_table_replace(chat->components, + g_strdup(QQ_ROOM_KEY_TITLE_UTF8), g_strdup(rmd->title_utf8)); +} + +static PurpleChat *chat_new(PurpleConnection *gc, qq_room_data *rmd) { GHashTable *components; PurpleGroup *g; PurpleChat *chat; - components = qq_group_to_hashtable(group); - chat = purple_chat_new(purple_connection_get_account(gc), group->title_utf8, components); - g = qq_get_purple_group(PURPLE_GROUP_QQ_QUN); + + purple_debug_info("QQ", "Add new chat: id %d, ext id %d, title %s\n", + rmd->id, rmd->ext_id, rmd->title_utf8); + + components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + g_hash_table_insert(components, + g_strdup(QQ_ROOM_KEY_INTERNAL_ID), g_strdup_printf("%d", rmd->id)); + g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_EXTERNAL_ID), + g_strdup_printf("%d", rmd->ext_id)); + g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_TITLE_UTF8), g_strdup(rmd->title_utf8)); + + chat = purple_chat_new(purple_connection_get_account(gc), rmd->title_utf8, components); + g = qq_group_find_or_new(PURPLE_GROUP_QQ_QUN); purple_blist_add_chat(chat, g, NULL); - purple_debug_info("QQ", "You have added group \"%s\" to blist locally\n", group->title_utf8); + + return chat; +} + +PurpleChat *qq_room_find_or_new(PurpleConnection *gc, guint32 id, guint32 ext_id) +{ + qq_data *qd; + qq_room_data *rmd; + PurpleChat *chat; + gchar *num_str; + + g_return_val_if_fail (gc != NULL && gc->proto_data != NULL, NULL); + qd = (qq_data *) gc->proto_data; + + g_return_val_if_fail(id != 0 && ext_id != 0, NULL); + + purple_debug_info("QQ", "Find or add new room: id %d, ext id %d\n", id, ext_id); + + rmd = qq_room_data_find(gc, id); + if (rmd == NULL) { + rmd = room_data_new(id, ext_id, NULL); + g_return_val_if_fail(rmd != NULL, NULL); + rmd->my_role = QQ_ROOM_ROLE_YES; + qd->groups = g_list_append(qd->groups, rmd); + } + + num_str = g_strdup_printf("%d", ext_id); + chat = purple_blist_find_chat(purple_connection_get_account(gc), num_str); + g_free(num_str); + if (chat) { + return chat; + } + + return chat_new(gc, rmd); } -/* Create a dummy qq_group, which includes only internal_id, ext_id, - * and potentially title_utf8, in case we need to call group_conv_show_window - * right after creation. All other attributes are set to empty. - * We need to send a get_group_info to the QQ server to update it right away */ -qq_group *qq_group_create_internal_record(PurpleConnection *gc, - guint32 internal_id, guint32 ext_id, gchar *title_utf8) +void qq_room_remove(PurpleConnection *gc, guint32 id) { - qq_group *group; - qq_data *qd; + qq_data *qd; + PurpleChat *chat; + qq_room_data *rmd; + gchar *num_str; + guint32 ext_id; - g_return_val_if_fail(internal_id > 0, NULL); - qd = (qq_data *) gc->proto_data; + g_return_if_fail (gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; - group = g_new0(qq_group, 1); - group->my_role = QQ_ROOM_ROLE_NO; - group->my_role_desc = get_role_desc(group); - group->id = internal_id; - group->ext_id = ext_id; - group->type8 = 0x01; /* assume permanent Qun */ - group->creator_uid = 10000; /* assume by QQ admin */ - group->category = 0x01; - group->auth_type = 0x02; /* assume need auth */ - group->title_utf8 = g_strdup(title_utf8 == NULL ? "" : title_utf8); - group->desc_utf8 = g_strdup(""); - group->notice_utf8 = g_strdup(""); - group->members = NULL; + purple_debug_info("QQ", "Find and remove room data, id %d", id); + rmd = qq_room_data_find(gc, id); + g_return_if_fail (rmd != NULL); + + ext_id = rmd->ext_id; + qd->groups = g_list_remove(qd->groups, rmd); + room_data_free(rmd); - qd->groups = g_list_append(qd->groups, group); - add_room_to_blist(gc, group); + purple_debug_info("QQ", "Find and remove chat, ext_id %d", ext_id); + num_str = g_strdup_printf("%d", ext_id); + chat = purple_blist_find_chat(purple_connection_get_account(gc), num_str); + g_free(num_str); - return group; + g_return_if_fail (chat != NULL); + + purple_blist_remove_chat(chat); } -void qq_group_delete_internal_record(qq_data *qd, guint32 id) +/* find a qq_buddy_data by uid, called by im.c */ +qq_buddy_data *qq_room_buddy_find(qq_room_data *rmd, guint32 uid) { - qq_group *group; - GList *list; + GList *list; + qq_buddy_data *bd; + g_return_val_if_fail(rmd != NULL && uid > 0, NULL); + + list = rmd->members; + while (list != NULL) { + bd = (qq_buddy_data *) list->data; + if (bd->uid == uid) + return bd; + else + list = list->next; + } - list = qd->groups; - while (list != NULL) { - group = (qq_group *) qd->groups->data; - if (id == group->id) { - qd->groups = g_list_remove(qd->groups, group); - qq_group_free(group); - break; - } else { - list = list->next; - } - } + return NULL; +} + +/* remove a qq_buddy_data by uid, called by qq_group_opt.c */ +void qq_room_buddy_remove(qq_room_data *rmd, guint32 uid) +{ + GList *list; + qq_buddy_data *bd; + g_return_if_fail(rmd != NULL && uid > 0); + + list = rmd->members; + while (list != NULL) { + bd = (qq_buddy_data *) list->data; + if (bd->uid == uid) { + rmd->members = g_list_remove(rmd->members, bd); + return; + } else { + list = list->next; + } + } } -/* convert a qq_group to hash-table, which could be component of PurpleChat */ -GHashTable *qq_group_to_hashtable(qq_group *group) +qq_buddy_data *qq_room_buddy_find_or_new(PurpleConnection *gc, qq_room_data *rmd, guint32 member_uid) { - GHashTable *components; - components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); - g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_ROLE), g_strdup_printf("%d", group->my_role)); - group->my_role_desc = get_role_desc(group); + qq_buddy_data *member, *bd; + PurpleBuddy *buddy; + g_return_val_if_fail(rmd != NULL && member_uid > 0, NULL); - g_hash_table_insert(components, - g_strdup(QQ_ROOM_KEY_INTERNAL_ID), g_strdup_printf("%d", group->id)); - g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_EXTERNAL_ID), - g_strdup_printf("%d", group->ext_id)); - g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_TYPE), g_strdup_printf("%d", group->type8)); - g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_CREATOR_UID), g_strdup_printf("%d", group->creator_uid)); - g_hash_table_insert(components, - g_strdup(QQ_ROOM_KEY_CATEGORY), g_strdup_printf("%d", group->category)); - g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_AUTH_TYPE), g_strdup_printf("%d", group->auth_type)); - g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_ROLE_DESC), g_strdup(group->my_role_desc)); - g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_TITLE_UTF8), g_strdup(group->title_utf8)); - g_hash_table_insert(components, g_strdup(QQ_ROOM_KEY_DESC_UTF8), g_strdup(group->desc_utf8)); - return components; + member = qq_room_buddy_find(rmd, member_uid); + if (member == NULL) { /* first appear during my session */ + member = g_new0(qq_buddy_data, 1); + member->uid = member_uid; + buddy = purple_find_buddy(purple_connection_get_account(gc), uid_to_purple_name(member_uid)); + if (buddy != NULL) { + bd = (qq_buddy_data *) buddy->proto_data; + if (bd != NULL && bd->nickname != NULL) + member->nickname = g_strdup(bd->nickname); + else if (buddy->alias != NULL) + member->nickname = g_strdup(buddy->alias); + } + rmd->members = g_list_append(rmd->members, member); + } + + return member; } -/* create a qq_group from hashtable */ -qq_group *qq_room_create_by_hashtable(PurpleConnection *gc, GHashTable *data) +qq_room_data *qq_room_data_find(PurpleConnection *gc, guint32 room_id) { + GList *list; + qq_room_data *rmd; qq_data *qd; - qq_group *group; + + qd = (qq_data *) gc->proto_data; + + if (qd->groups == NULL || room_id <= 0) + return 0; - g_return_val_if_fail(data != NULL, NULL); + list = qd->groups; + while (list != NULL) { + rmd = (qq_room_data *) list->data; + if (rmd->id == room_id) { + return rmd; + } + list = list->next; + } + + return NULL; +} + +guint32 qq_room_get_next(PurpleConnection *gc, guint32 room_id) +{ + GList *list; + qq_room_data *rmd; + qq_data *qd; + gboolean is_find = FALSE; + qd = (qq_data *) gc->proto_data; - group = g_new0(qq_group, 1); - group->my_role = - qq_string_to_dec_value - (NULL == - g_hash_table_lookup(data, - QQ_ROOM_KEY_ROLE) ? - g_strdup_printf("%d", QQ_ROOM_ROLE_NO) : - g_hash_table_lookup(data, QQ_ROOM_KEY_ROLE)); - group->id = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_ROOM_KEY_INTERNAL_ID)); - group->ext_id = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_ROOM_KEY_EXTERNAL_ID)); - group->type8 = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_ROOM_KEY_TYPE)); - group->creator_uid = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_ROOM_KEY_CREATOR_UID)); - group->category = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_ROOM_KEY_CATEGORY)); - group->auth_type = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_ROOM_KEY_AUTH_TYPE)); - group->title_utf8 = g_strdup(g_hash_table_lookup(data, QQ_ROOM_KEY_TITLE_UTF8)); - group->desc_utf8 = g_strdup(g_hash_table_lookup(data, QQ_ROOM_KEY_DESC_UTF8)); - group->my_role_desc = get_role_desc(group); - group->is_got_info = FALSE; + if (qd->groups == NULL) { + return 0; + } + + if (room_id <= 0) { + rmd = (qq_room_data *) qd->groups->data; + return rmd->id; + } - qd->groups = g_list_append(qd->groups, group); - return group; + list = qd->groups; + while (list != NULL) { + rmd = (qq_room_data *) list->data; + list = list->next; + if (rmd->id == room_id) { + is_find = TRUE; + break; + } + } + + g_return_val_if_fail(is_find, 0); + if (list == NULL) return 0; /* be the end */ + rmd = (qq_room_data *) list->data; + g_return_val_if_fail(rmd != NULL, 0); + return rmd->id; } -/* refresh group local subscription */ -void qq_group_refresh(PurpleConnection *gc, qq_group *group) +guint32 qq_room_get_next_conv(PurpleConnection *gc, guint32 room_id) { + GList *list; + qq_room_data *rmd; + qq_data *qd; + gboolean is_find; + + qd = (qq_data *) gc->proto_data; + + list = qd->groups; + if (room_id > 0) { + /* search next room */ + is_find = FALSE; + while (list != NULL) { + rmd = (qq_room_data *) list->data; + list = list->next; + if (rmd->id == room_id) { + is_find = TRUE; + break; + } + } + g_return_val_if_fail(is_find, 0); + } + + while (list != NULL) { + rmd = (qq_room_data *) list->data; + g_return_val_if_fail(rmd != NULL, 0); + + if (rmd->my_role == QQ_ROOM_ROLE_YES || rmd->my_role == QQ_ROOM_ROLE_ADMIN) { + if (NULL != purple_find_conversation_with_account( + PURPLE_CONV_TYPE_CHAT,rmd->title_utf8, purple_connection_get_account(gc))) { + /* In convseration*/ + return rmd->id; + } + } + list = list->next; + } + + return 0; +} + +/* this should be called upon signin, even when we did not open group chat window */ +void qq_room_data_initial(PurpleConnection *gc) +{ + PurpleAccount *account; PurpleChat *chat; - gchar *ext_id; - g_return_if_fail(group != NULL); + PurpleGroup *purple_group; + PurpleBlistNode *node; + qq_data *qd; + qq_room_data *rmd; + gint count; - ext_id = g_strdup_printf("%d", group->ext_id); - chat = purple_blist_find_chat(purple_connection_get_account(gc), ext_id); - g_free(ext_id); - if (chat == NULL && group->my_role != QQ_ROOM_ROLE_NO) { - add_room_to_blist(gc, group); + account = purple_connection_get_account(gc); + qd = (qq_data *) gc->proto_data; + + purple_debug_info("QQ", "Initial QQ Qun configurations\n"); + purple_group = purple_find_group(PURPLE_GROUP_QQ_QUN); + if (purple_group == NULL) { + purple_debug_info("QQ", "We have no QQ Qun\n"); return; } - if (chat == NULL) { - return; + count = 0; + for (node = ((PurpleBlistNode *) purple_group)->child; node != NULL; node = node->next) { + if ( !PURPLE_BLIST_NODE_IS_CHAT(node)) { + continue; + } + /* got one */ + chat = (PurpleChat *) node; + if (account != chat->account) /* not qq account*/ + continue; + + rmd = room_data_new_by_hashtable(gc, chat->components); + qd->groups = g_list_append(qd->groups, rmd); + count++; } - /* we have a local record, update its info */ - /* if there is title_utf8, we update the group name */ - if (group->title_utf8 != NULL && strlen(group->title_utf8) > 0) - purple_blist_alias_chat(chat, group->title_utf8); - g_hash_table_replace(chat->components, - g_strdup(QQ_ROOM_KEY_ROLE), g_strdup_printf("%d", group->my_role)); - group->my_role_desc = get_role_desc(group); - g_hash_table_replace(chat->components, - g_strdup(QQ_ROOM_KEY_ROLE_DESC), g_strdup(group->my_role_desc)); - g_hash_table_replace(chat->components, - g_strdup(QQ_ROOM_KEY_INTERNAL_ID), - g_strdup_printf("%d", group->id)); - g_hash_table_replace(chat->components, - g_strdup(QQ_ROOM_KEY_EXTERNAL_ID), - g_strdup_printf("%d", group->ext_id)); - g_hash_table_replace(chat->components, - g_strdup(QQ_ROOM_KEY_TYPE), g_strdup_printf("%d", group->type8)); - g_hash_table_replace(chat->components, - g_strdup(QQ_ROOM_KEY_CREATOR_UID), g_strdup_printf("%d", group->creator_uid)); - g_hash_table_replace(chat->components, - g_strdup(QQ_ROOM_KEY_CATEGORY), - g_strdup_printf("%d", group->category)); - g_hash_table_replace(chat->components, - g_strdup(QQ_ROOM_KEY_AUTH_TYPE), g_strdup_printf("%d", group->auth_type)); - g_hash_table_replace(chat->components, - g_strdup(QQ_ROOM_KEY_TITLE_UTF8), g_strdup(group->title_utf8)); - g_hash_table_replace(chat->components, - g_strdup(QQ_ROOM_KEY_DESC_UTF8), g_strdup(group->desc_utf8)); + purple_debug_info("QQ", "Load %d QQ Qun configurations\n", count); } -/* NOTE: If we knew how to convert between an external and internal group id, as the official - * client seems to, the following would be unnecessary. That would be ideal. */ - -/* Use list to specify if id's alternate id is pending discovery. */ -void qq_set_pending_id(GSList **list, guint32 id, gboolean pending) +void qq_room_data_free_all(PurpleConnection *gc) { - if (pending) - *list = g_slist_prepend(*list, GINT_TO_POINTER(id)); - else - *list = g_slist_remove(*list, GINT_TO_POINTER(id)); -} + qq_data *qd; + qq_room_data *rmd; + gint count; + + g_return_if_fail (gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; -/* Return the location of id in list, or NULL if not found */ -GSList *qq_get_pending_id(GSList *list, guint32 id) -{ - return g_slist_find(list, GINT_TO_POINTER(id)); + count = 0; + while (qd->groups != NULL) { + rmd = (qq_room_data *) qd->groups->data; + qd->groups = g_list_remove(qd->groups, rmd); + room_data_free(rmd); + count++; + } + + if (count > 0) { + purple_debug_info("QQ", "%d rooms are freed\n", count); + } } diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group_internal.h --- a/libpurple/protocols/qq/group_internal.h Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/group_internal.h Wed Oct 29 21:38:53 2008 +0000 @@ -28,27 +28,23 @@ #include #include "group.h" -#define QQ_ROOM_KEY_ROLE "my_role" -#define QQ_ROOM_KEY_ROLE_DESC "my_role_desc" #define QQ_ROOM_KEY_INTERNAL_ID "id" #define QQ_ROOM_KEY_EXTERNAL_ID "ext_id" -#define QQ_ROOM_KEY_TYPE "type" -#define QQ_ROOM_KEY_CREATOR_UID "creator_uid" -#define QQ_ROOM_KEY_CATEGORY "category" -#define QQ_ROOM_KEY_AUTH_TYPE "auth_type" -#define QQ_ROOM_KEY_TITLE_UTF8 "title_utf8" -#define QQ_ROOM_KEY_DESC_UTF8 "desc_utf8" +#define QQ_ROOM_KEY_TITLE_UTF8 "title_utf8" + +PurpleChat *qq_room_find_or_new(PurpleConnection *gc, guint32 id, guint32 ext_id); +void qq_room_remove(PurpleConnection *gc, guint32 id); +void qq_room_update_chat_info(PurpleChat *chat, qq_room_data *rmd); -qq_group *qq_group_create_internal_record(PurpleConnection *gc, - guint32 internal_id, guint32 ext_id, gchar *group_name_utf8); -void qq_group_delete_internal_record(qq_data *qd, guint32 id); +qq_buddy_data *qq_room_buddy_find(qq_room_data *rmd, guint32 uid); +void qq_room_buddy_remove(qq_room_data *rmd, guint32 uid); +qq_buddy_data *qq_room_buddy_find_or_new(PurpleConnection *gc, qq_room_data *rmd, guint32 member_uid); -GHashTable *qq_group_to_hashtable(qq_group *group); -qq_group *qq_room_create_by_hashtable(PurpleConnection *gc, GHashTable *data); +void qq_room_data_initial(PurpleConnection *gc); +void qq_room_data_free_all(PurpleConnection *gc); +qq_room_data *qq_room_data_find(PurpleConnection *gc, guint32 room_id); -void qq_group_refresh(PurpleConnection *gc, qq_group *group); - -void qq_set_pending_id(GSList **list, guint32 id, gboolean pending); -GSList *qq_get_pending_id(GSList *list, guint32 id); +guint32 qq_room_get_next(PurpleConnection *gc, guint32 room_id); +guint32 qq_room_get_next_conv(PurpleConnection *gc, guint32 room_id); #endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group_join.c --- a/libpurple/protocols/qq/group_join.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/group_join.c Wed Oct 29 21:38:53 2008 +0000 @@ -29,18 +29,14 @@ #include "request.h" #include "server.h" -#include "buddy_opt.h" #include "char_conv.h" -#include "group_conv.h" -#include "group_find.h" -#include "group_free.h" +#include "im.h" #include "group_internal.h" #include "group_info.h" #include "group_join.h" #include "group_opt.h" -#include "group_conv.h" -#include "group_search.h" -#include "header_info.h" +#include "group_im.h" +#include "qq_define.h" #include "packet_parse.h" #include "qq_network.h" #include "qq_process.h" @@ -51,129 +47,140 @@ QQ_ROOM_JOIN_DENIED = 0x03, }; -static void _qq_group_exit_with_gc_and_id(gc_and_uid *g) +enum { + QQ_ROOM_SEARCH_TYPE_BY_ID = 0x01, + QQ_ROOM_SEARCH_TYPE_DEMO = 0x02 +}; + +static void group_quit_cb(qq_room_req *add_req) { PurpleConnection *gc; guint32 id; - qq_group *group; + qq_room_data *rmd; - gc = g->gc; - id = g->uid; + if (add_req->gc == NULL || add_req->id == 0) { + g_free(add_req); + return; + } - group = qq_room_search_id(gc, id); - g_return_if_fail(group != NULL); + gc = add_req->gc; + id = add_req->id; - qq_send_room_cmd_only(gc, QQ_ROOM_CMD_QUIT, group->id); + rmd = qq_room_data_find(gc, id); + if (rmd == NULL) { + g_free(add_req); + return; + } + + qq_send_room_cmd_only(gc, QQ_ROOM_CMD_QUIT, rmd->id); + g_free(add_req); } /* send packet to join a group without auth */ -void qq_request_room_join(PurpleConnection *gc, qq_group *group) +void qq_request_room_join(PurpleConnection *gc, qq_room_data *rmd) { - g_return_if_fail(group != NULL); + g_return_if_fail(rmd != NULL); - if (group->my_role == QQ_ROOM_ROLE_NO) { - group->my_role = QQ_ROOM_ROLE_REQUESTING; - qq_group_refresh(gc, group); + if (rmd->my_role == QQ_ROOM_ROLE_NO) { + rmd->my_role = QQ_ROOM_ROLE_REQUESTING; } - switch (group->auth_type) { + switch (rmd->auth_type) { case QQ_ROOM_AUTH_TYPE_NO_AUTH: case QQ_ROOM_AUTH_TYPE_NEED_AUTH: break; case QQ_ROOM_AUTH_TYPE_NO_ADD: - if (group->my_role == QQ_ROOM_ROLE_NO - && group->my_role == QQ_ROOM_ROLE_REQUESTING) { + if (rmd->my_role == QQ_ROOM_ROLE_NO + && rmd->my_role == QQ_ROOM_ROLE_REQUESTING) { purple_notify_warning(gc, NULL, _("The Qun does not allow others to join"), NULL); return; } break; default: - purple_debug_error("QQ", "Unknown room auth type: %d\n", group->auth_type); + purple_debug_error("QQ", "Unknown room auth type: %d\n", rmd->auth_type); break; } - qq_send_room_cmd_only(gc, QQ_ROOM_CMD_JOIN, group->id); + qq_send_room_cmd_only(gc, QQ_ROOM_CMD_JOIN, rmd->id); } -static void _qq_group_join_auth_with_gc_and_id(gc_and_uid *g, const gchar *reason_utf8) +static void group_join_cb(qq_room_req *add_req, const gchar *reason_utf8) { - PurpleConnection *gc; - qq_group *group; - guint32 id; + qq_room_data *rmd; + + g_return_if_fail(add_req != NULL); + if (add_req->gc == NULL || add_req->id == 0) { + g_free(add_req); + return; + } - gc = g->gc; - id = g->uid; + rmd = qq_room_data_find(add_req->gc, add_req->id); + if (rmd == NULL) { + purple_debug_error("QQ", "Can not find room data of %d\n", add_req->id); + g_free(add_req); + return; + } - group = qq_room_search_id(gc, id); - if (group == NULL) { - purple_debug_error("QQ", "Can not find qq_group by internal_id: %d\n", id); - return; - } else { /* everything is OK */ - qq_send_cmd_group_auth(gc, group, QQ_ROOM_AUTH_REQUEST_APPLY, 0, reason_utf8); - } + qq_send_cmd_group_auth(add_req->gc, rmd, QQ_ROOM_AUTH_REQUEST_APPLY, 0, reason_utf8); + g_free(add_req); } -static void _qq_group_join_auth(PurpleConnection *gc, qq_group *group) +static void room_join_cancel_cb(qq_room_req *add_req, const gchar *msg) +{ + g_return_if_fail(add_req != NULL); + g_free(add_req); +} + +static void do_room_join_request(PurpleConnection *gc, qq_room_data *rmd) { gchar *msg; - gc_and_uid *g; - g_return_if_fail(group != NULL); + qq_room_req *add_req; + g_return_if_fail(rmd != NULL); - purple_debug_info("QQ", "Group (internal id: %d) needs authentication\n", group->id); + purple_debug_info("QQ", "Room (internal id: %d) needs authentication\n", rmd->id); - msg = g_strdup_printf("Group \"%s\" needs authentication\n", group->title_utf8); - g = g_new0(gc_and_uid, 1); - g->gc = gc; - g->uid = group->id; - purple_request_input(gc, NULL, msg, + msg = g_strdup_printf("QQ Qun %d needs authentication\n", rmd->ext_id); + add_req = g_new0(qq_room_req, 1); + add_req->gc = gc; + add_req->id = rmd->id; + purple_request_input(gc, _("Join QQ Qun"), msg, _("Input request here"), _("Would you be my friend?"), TRUE, FALSE, NULL, _("Send"), - G_CALLBACK(_qq_group_join_auth_with_gc_and_id), - _("Cancel"), G_CALLBACK(qq_do_nothing_with_gc_and_uid), - purple_connection_get_account(gc), group->title_utf8, NULL, - g); + G_CALLBACK(group_join_cb), + _("Cancel"), G_CALLBACK(room_join_cancel_cb), + purple_connection_get_account(gc), rmd->title_utf8, NULL, + add_req); g_free(msg); } -void qq_send_cmd_group_auth(PurpleConnection *gc, qq_group *group, guint8 opt, guint32 uid, const gchar *reason_utf8) +void qq_send_cmd_group_auth(PurpleConnection *gc, qq_room_data *rmd, + guint8 opt, guint32 uid, const gchar *reason_utf8) { - guint8 *raw_data; - gchar *reason_qq; + guint8 raw_data[MAX_PACKET_SIZE - 16]; gint bytes; - g_return_if_fail(group != NULL); - - if (reason_utf8 == NULL || strlen(reason_utf8) == 0) - reason_qq = g_strdup(""); - else - reason_qq = utf8_to_qq(reason_utf8, QQ_CHARSET_DEFAULT); + g_return_if_fail(rmd != NULL); if (opt == QQ_ROOM_AUTH_REQUEST_APPLY) { - group->my_role = QQ_ROOM_ROLE_REQUESTING; - qq_group_refresh(gc, group); + rmd->my_role = QQ_ROOM_ROLE_REQUESTING; uid = 0; } - raw_data = g_newa(guint8, 6 + strlen(reason_qq)); - bytes = 0; bytes += qq_put8(raw_data + bytes, opt); bytes += qq_put32(raw_data + bytes, uid); - bytes += qq_put8(raw_data + bytes, strlen(reason_qq)); - bytes += qq_putdata(raw_data + bytes, (guint8 *) reason_qq, strlen(reason_qq)); + bytes += qq_put_vstr(raw_data + bytes, reason_utf8, QQ_CHARSET_DEFAULT); - qq_send_room_cmd(gc, QQ_ROOM_CMD_AUTH, group->id, raw_data, bytes); + qq_send_room_cmd(gc, QQ_ROOM_CMD_AUTH, rmd->id, raw_data, bytes); } /* If comes here, cmd is OK already */ void qq_process_group_cmd_exit_group(guint8 *data, gint len, PurpleConnection *gc) { + qq_data *qd; gint bytes; guint32 id; - PurpleChat *chat; - qq_group *group; - qq_data *qd; g_return_if_fail(data != NULL && len > 0); qd = (qq_data *) gc->proto_data; @@ -186,15 +193,7 @@ bytes = 0; bytes += qq_get32(&id, data + bytes); - group = qq_room_search_id(gc, id); - if (group != NULL) { - chat = purple_blist_find_chat - (purple_connection_get_account(gc), g_strdup_printf("%d", group->ext_id)); - if (chat != NULL) - purple_blist_remove_chat(chat); - qq_group_delete_internal_record(qd, id); - } - purple_notify_info(gc, _("QQ Qun Operation"), _("Successed:"), _("Remove from Qun")); + qq_room_remove(gc, id); } /* Process the reply to group_auth subcmd */ @@ -203,6 +202,8 @@ gint bytes; guint32 id; qq_data *qd; + qq_room_data *rmd; + gchar *msg; g_return_if_fail(data != NULL && len > 0); qd = (qq_data *) gc->proto_data; @@ -216,7 +217,14 @@ bytes += qq_get32(&id, data + bytes); g_return_if_fail(id > 0); - purple_notify_info(gc, _("QQ Qun Operation"), _("Successed:"), _("Join to Qun")); + rmd = qq_room_data_find(gc, id); + if (rmd != NULL) { + msg = g_strdup_printf(_("Successed join to Qun %s (%d)"), rmd->title_utf8, rmd->ext_id); + qq_got_attention(gc, msg); + g_free(msg); + } else { + qq_got_attention(gc, _("Successed join to Qun")); + } } /* process group cmd reply "join group" */ @@ -225,14 +233,14 @@ gint bytes; guint32 id; guint8 reply; - qq_group *group; + qq_room_data *rmd; gchar *msg; g_return_if_fail(data != NULL && len > 0); if (len < 5) { purple_debug_error("QQ", - "Invalid join group reply, expect %d bytes, read %d bytes\n", 5, len); + "Invalid join room reply, expect %d bytes, read %d bytes\n", 5, len); return; } @@ -241,34 +249,32 @@ bytes += qq_get8(&reply, data + bytes); /* join group OK */ - group = qq_room_search_id(gc, id); + rmd = qq_room_data_find(gc, id); /* need to check if group is NULL or not. */ - g_return_if_fail(group != NULL); + g_return_if_fail(rmd != NULL); switch (reply) { case QQ_ROOM_JOIN_OK: - purple_debug_info("QQ", "Successed in joining group \"%s\"\n", group->title_utf8); - group->my_role = QQ_ROOM_ROLE_YES; - qq_group_refresh(gc, group); + purple_debug_info("QQ", "Successed in joining group \"%s\"\n", rmd->title_utf8); + rmd->my_role = QQ_ROOM_ROLE_YES; /* this must be shown before getting online members */ - qq_room_conv_create(gc, group); + qq_room_conv_open(gc, rmd); break; case QQ_ROOM_JOIN_NEED_AUTH: purple_debug_info("QQ", "Fail joining group [%d] %s, needs authentication\n", - group->ext_id, group->title_utf8); - group->my_role = QQ_ROOM_ROLE_NO; - qq_group_refresh(gc, group); - _qq_group_join_auth(gc, group); + rmd->ext_id, rmd->title_utf8); + rmd->my_role = QQ_ROOM_ROLE_NO; + do_room_join_request(gc, rmd); break; case QQ_ROOM_JOIN_DENIED: - msg = g_strdup_printf(_("Qun %d denied to join"), group->ext_id); + msg = g_strdup_printf(_("Qun %d denied to join"), rmd->ext_id); purple_notify_info(gc, _("QQ Qun Operation"), _("Failed:"), msg); g_free(msg); break; default: purple_debug_info("QQ", "Failed joining group [%d] %s, unknown reply: 0x%02x\n", - group->ext_id, group->title_utf8, reply); + rmd->ext_id, rmd->title_utf8, reply); purple_notify_info(gc, _("QQ Qun Operation"), _("Failed:"), _("Join Qun, Unknow Reply")); } @@ -278,55 +284,146 @@ void qq_group_join(PurpleConnection *gc, GHashTable *data) { qq_data *qd; - gchar *ext_id_ptr; + gchar *ext_id_str; + gchar *id_str; guint32 ext_id; - qq_group *group; + guint32 id; + qq_room_data *rmd; g_return_if_fail(data != NULL); qd = (qq_data *) gc->proto_data; - ext_id_ptr = g_hash_table_lookup(data, QQ_ROOM_KEY_EXTERNAL_ID); - g_return_if_fail(ext_id_ptr != NULL); - errno = 0; - ext_id = strtol(ext_id_ptr, NULL, 10); - if (errno != 0) { - purple_notify_error(gc, _("Error"), - _("You entered a group ID outside the acceptable range"), NULL); + ext_id_str = g_hash_table_lookup(data, QQ_ROOM_KEY_EXTERNAL_ID); + id_str = g_hash_table_lookup(data, QQ_ROOM_KEY_INTERNAL_ID); + purple_debug_info("QQ", "Join room %s, extend id %s\n", id_str, ext_id_str); + + if (id_str != NULL) { + id = strtol(id_str, NULL, 10); + if (id != 0) { + rmd = qq_room_data_find(gc, id); + if (rmd) { + qq_request_room_join(gc, rmd); + return; + } + } + } + + purple_debug_info("QQ", "Search and join extend id %s\n", ext_id_str); + if (ext_id_str == NULL) { + return; + } + ext_id = strtol(ext_id_str, NULL, 10); + if (ext_id == 0) { return; } - group = qq_room_search_ext_id(gc, ext_id); - if (group) { - qq_request_room_join(gc, group); - } else { - qq_set_pending_id(&qd->joining_groups, ext_id, TRUE); - qq_send_cmd_group_search_group(gc, ext_id); - } + qq_request_room_search(gc, ext_id, QQ_ROOM_SEARCH_FOR_JOIN); } -void qq_group_exit(PurpleConnection *gc, GHashTable *data) +void qq_room_quit(PurpleConnection *gc, guint32 room_id) { - gchar *id_ptr; - guint32 id; - gc_and_uid *g; - - g_return_if_fail(data != NULL); + qq_room_req *add_req; - id_ptr = g_hash_table_lookup(data, QQ_ROOM_KEY_INTERNAL_ID); - id = strtol(id_ptr, NULL, 10); - - g_return_if_fail(id > 0); - - g = g_new0(gc_and_uid, 1); - g->gc = gc; - g->uid = id; + add_req = g_new0(qq_room_req, 1); + add_req->gc = gc; + add_req->id = room_id; purple_request_action(gc, _("QQ Qun Operation"), - _("Are you sure you want to leave this Qun?"), + _("Quit Qun"), _("Note, if you are the creator, \nthis operation will eventually remove this Qun."), 1, purple_connection_get_account(gc), NULL, NULL, - g, 2, _("Cancel"), - G_CALLBACK(qq_do_nothing_with_gc_and_uid), - _("Continue"), G_CALLBACK(_qq_group_exit_with_gc_and_id)); + add_req, 2, _("Cancel"), + G_CALLBACK(room_join_cancel_cb), + _("Continue"), G_CALLBACK(group_quit_cb)); +} + +/* send packet to search for qq_group */ +void qq_request_room_search(PurpleConnection *gc, guint32 ext_id, int action) +{ + guint8 raw_data[16] = {0}; + gint bytes = 0; + guint8 type; + + purple_debug_info("QQ", "Search QQ Qun %d\n", ext_id); + type = (ext_id == 0x00000000) ? QQ_ROOM_SEARCH_TYPE_DEMO : QQ_ROOM_SEARCH_TYPE_BY_ID; + + bytes = 0; + bytes += qq_put8(raw_data + bytes, type); + bytes += qq_put32(raw_data + bytes, ext_id); + + qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_SEARCH, 0, raw_data, bytes, 0, action); +} + +static void add_to_roomlist(qq_data *qd, qq_room_data *rmd) +{ + PurpleRoomlistRoom *room; + gchar field[11]; + + room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, rmd->title_utf8, NULL); + g_snprintf(field, sizeof(field), "%d", rmd->ext_id); + purple_roomlist_room_add_field(qd->roomlist, room, field); + g_snprintf(field, sizeof(field), "%d", rmd->creator_uid); + purple_roomlist_room_add_field(qd->roomlist, room, field); + purple_roomlist_room_add_field(qd->roomlist, room, rmd->desc_utf8); + g_snprintf(field, sizeof(field), "%d", rmd->id); + purple_roomlist_room_add_field(qd->roomlist, room, field); + g_snprintf(field, sizeof(field), "%d", rmd->type8); + purple_roomlist_room_add_field(qd->roomlist, room, field); + g_snprintf(field, sizeof(field), "%d", rmd->auth_type); + purple_roomlist_room_add_field(qd->roomlist, room, field); + g_snprintf(field, sizeof(field), "%d", rmd->category); + purple_roomlist_room_add_field(qd->roomlist, room, field); + purple_roomlist_room_add_field(qd->roomlist, room, rmd->title_utf8); + purple_roomlist_room_add(qd->roomlist, room); + + purple_roomlist_set_in_progress(qd->roomlist, FALSE); } + +/* process group cmd reply "search group" */ +void qq_process_room_search(PurpleConnection *gc, guint8 *data, gint len, guint32 ship32) +{ + qq_data *qd; + qq_room_data rmd; + PurpleChat *chat; + gint bytes; + guint8 search_type; + guint16 unknown; + + g_return_if_fail(data != NULL && len > 0); + qd = (qq_data *) gc->proto_data; + + bytes = 0; + bytes += qq_get8(&search_type, data + bytes); + + /* now it starts with group_info_entry */ + bytes += qq_get32(&(rmd.id), data + bytes); + bytes += qq_get32(&(rmd.ext_id), data + bytes); + bytes += qq_get8(&(rmd.type8), data + bytes); + bytes += qq_get16(&(unknown), data + bytes); + bytes += qq_get16(&(unknown), data + bytes); + bytes += qq_get32(&(rmd.creator_uid), data + bytes); + bytes += qq_get16(&(unknown), data + bytes); + bytes += qq_get16(&(unknown), data + bytes); + bytes += qq_get16(&(unknown), data + bytes); + bytes += qq_get32(&(rmd.category), data + bytes); + bytes += qq_get_vstr(&(rmd.title_utf8), QQ_CHARSET_DEFAULT, data + bytes); + bytes += qq_get16(&(unknown), data + bytes); + bytes += qq_get8(&(rmd.auth_type), data + bytes); + bytes += qq_get_vstr(&(rmd.desc_utf8), QQ_CHARSET_DEFAULT, data + bytes); + /* end of one qq_group */ + if(bytes != len) { + purple_debug_error("QQ", + "group_cmd_search_group: Dangerous error! maybe protocol changed, notify developers!"); + } + + if (ship32 == QQ_ROOM_SEARCH_FOR_JOIN) { + chat = qq_room_find_or_new(gc, rmd.id, rmd.ext_id); + g_return_if_fail(chat != NULL); + + qq_room_update_chat_info(chat, &rmd); + qq_request_room_join(gc, &rmd); + } else { + add_to_roomlist(qd, &rmd); + } +} diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group_join.h --- a/libpurple/protocols/qq/group_join.h Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/group_join.h Wed Oct 29 21:38:53 2008 +0000 @@ -41,12 +41,19 @@ QQ_ROOM_AUTH_REQUEST_REJECT = 0x03 }; -void qq_send_cmd_group_auth(PurpleConnection *gc, qq_group *group, guint8 opt, guint32 uid, const gchar *reason_utf8); +enum { + QQ_ROOM_SEARCH_ONLY = 0, + QQ_ROOM_SEARCH_FOR_JOIN +}; + +void qq_request_room_search(PurpleConnection *gc, guint32 ext_id, int action); +void qq_process_room_search(PurpleConnection *gc, guint8 *data, gint len, guint32 ship32); + +void qq_send_cmd_group_auth(PurpleConnection *gc, qq_room_data *rmd, guint8 opt, guint32 uid, const gchar *reason_utf8); void qq_group_join(PurpleConnection *gc, GHashTable *data); -void qq_request_room_join(PurpleConnection *gc, qq_group *group); -void qq_group_exit(PurpleConnection *gc, GHashTable *data); +void qq_request_room_join(PurpleConnection *gc, qq_room_data *rmd); +void qq_room_quit(PurpleConnection *gc, guint32 room_id); void qq_process_group_cmd_exit_group(guint8 *data, gint len, PurpleConnection *gc); void qq_process_group_cmd_join_group_auth(guint8 *data, gint len, PurpleConnection *gc); void qq_process_group_cmd_join_group(guint8 *data, gint len, PurpleConnection *gc); - #endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group_opt.c --- a/libpurple/protocols/qq/group_opt.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/group_opt.c Wed Oct 29 21:38:53 2008 +0000 @@ -30,12 +30,12 @@ #include "buddy_info.h" #include "char_conv.h" -#include "group_find.h" #include "group_internal.h" #include "group_info.h" #include "group_join.h" +#include "group_im.h" #include "group_opt.h" -#include "header_info.h" +#include "qq_define.h" #include "packet_parse.h" #include "qq_network.h" #include "qq_process.h" @@ -57,7 +57,7 @@ qsort (list, i, sizeof (guint32), _compare_guint32); } -static void _qq_group_member_opt(PurpleConnection *gc, qq_group *group, gint operation, guint32 *members) +static void _qq_group_member_opt(PurpleConnection *gc, qq_room_data *rmd, gint operation, guint32 *members) { guint8 *data; gint i, count, data_len; @@ -74,80 +74,71 @@ for (i = 0; i < count; i++) bytes += qq_put32(data + bytes, members[i]); - qq_send_room_cmd(gc, QQ_ROOM_CMD_MEMBER_OPT, group->id, data, bytes); + qq_send_room_cmd(gc, QQ_ROOM_CMD_MEMBER_OPT, rmd->id, data, bytes); } -static void _qq_group_do_nothing_with_struct(group_member_opt *g) +static void room_req_cancel_cb(qq_room_req *add_req) { - if (g != NULL) - g_free(g); + if (add_req != NULL) + g_free(add_req); } -static void _qq_group_reject_application_real(group_member_opt *g, gchar *msg_utf8) +static void member_join_authorize_cb(gpointer data) { - qq_group *group; - g_return_if_fail(g != NULL && g->gc != NULL && g->id > 0 && g->member > 0); - group = qq_room_search_id(g->gc, g->id); - g_return_if_fail(group != NULL); - qq_send_cmd_group_auth(g->gc, group, QQ_ROOM_AUTH_REQUEST_REJECT, g->member, msg_utf8); - g_free(g); -} + qq_room_req *add_req = (qq_room_req *)data; + qq_room_data *rmd; + g_return_if_fail(add_req != NULL && add_req->gc != NULL); + g_return_if_fail(add_req->id > 0 && add_req->member > 0); + rmd = qq_room_data_find(add_req->gc, add_req->id); + g_return_if_fail(rmd != NULL); -void qq_group_search_application_with_struct(group_member_opt *g) -{ - g_return_if_fail(g != NULL && g->gc != NULL && g->member > 0); - - qq_send_packet_get_info(g->gc, g->member, TRUE); /* we want to see window */ - purple_request_action(g->gc, NULL, _("Do you want to approve the request?"), "", - PURPLE_DEFAULT_ACTION_NONE, - purple_connection_get_account(g->gc), NULL, NULL, - g, 2, - _("Reject"), G_CALLBACK(qq_group_reject_application_with_struct), - _("Approve"), G_CALLBACK(qq_group_approve_application_with_struct)); + qq_send_cmd_group_auth(add_req->gc, rmd, QQ_ROOM_AUTH_REQUEST_APPROVE, add_req->member, ""); + qq_room_buddy_find_or_new(add_req->gc, rmd, add_req->member); + g_free(add_req); } -void qq_group_reject_application_with_struct(group_member_opt *g) +static void member_join_deny_reason_cb(qq_room_req *add_req, gchar *msg_utf8) { - gchar *msg1, *msg2, *nombre; - g_return_if_fail(g != NULL && g->gc != NULL && g->member > 0); - - msg1 = g_strdup_printf(_("You rejected %d's request"), g->member); - msg2 = g_strdup(_("Message:")); + qq_room_data *rmd; + g_return_if_fail(add_req != NULL && add_req->gc != NULL); + g_return_if_fail(add_req->id > 0 && add_req->member > 0); + rmd = qq_room_data_find(add_req->gc, add_req->id); + g_return_if_fail(rmd != NULL); + qq_send_cmd_group_auth(add_req->gc, rmd, QQ_ROOM_AUTH_REQUEST_REJECT, add_req->member, msg_utf8); + g_free(add_req); +} - nombre = uid_to_purple_name(g->member); - purple_request_input(g->gc, /* title */ NULL, msg1, msg2, - _("Sorry, you are not my style..."), /* multiline */ TRUE, /* masked */ FALSE, - /* hint */ NULL, - _("Send"), G_CALLBACK(_qq_group_reject_application_real), - _("Cancel"), G_CALLBACK(_qq_group_do_nothing_with_struct), - purple_connection_get_account(g->gc), nombre, NULL, - g); - - g_free(msg1); - g_free(msg2); - g_free(nombre); +static void member_join_deny_noreason_cb(qq_room_req *add_req, gchar *msg_utf8) +{ + member_join_deny_reason_cb(add_req, NULL); } -void qq_group_approve_application_with_struct(group_member_opt *g) +static void member_join_deny_cb(gpointer data) { - qq_group *group; - g_return_if_fail(g != NULL && g->gc != NULL && g->id > 0 && g->member > 0); - group = qq_room_search_id(g->gc, g->id); - g_return_if_fail(group != NULL); - qq_send_cmd_group_auth(g->gc, group, QQ_ROOM_AUTH_REQUEST_APPROVE, g->member, ""); - qq_group_find_or_add_member(g->gc, group, g->member); - g_free(g); + qq_room_req *add_req = (qq_room_req *)data; + gchar *who; + g_return_if_fail(add_req != NULL && add_req->gc != NULL); + g_return_if_fail(add_req->id > 0 && add_req->member > 0); + + who = uid_to_purple_name(add_req->member); + purple_request_input(add_req->gc, NULL, _("Authorization denied message:"), + NULL, _("Sorry, you are not our style ..."), TRUE, FALSE, NULL, + _("OK"), G_CALLBACK(member_join_deny_reason_cb), + _("Cancel"), G_CALLBACK(member_join_deny_noreason_cb), + purple_connection_get_account(add_req->gc), who, NULL, + add_req); + g_free(who); } -void qq_group_modify_members(PurpleConnection *gc, qq_group *group, guint32 *new_members) +void qq_group_modify_members(PurpleConnection *gc, qq_room_data *rmd, guint32 *new_members) { guint32 *old_members, *del_members, *add_members; - qq_buddy *q_bud; + qq_buddy_data *bd; qq_data *qd; gint i = 0, old = 0, new = 0, del = 0, add = 0; GList *list; - g_return_if_fail(group != NULL); + g_return_if_fail(rmd != NULL); qd = (qq_data *) gc->proto_data; if (new_members[0] == 0xffffffff) return; @@ -157,11 +148,11 @@ add_members = g_newa(guint32, QQ_QUN_MEMBER_MAX); /* construct the old member list */ - list = group->members; + list = rmd->members; while (list != NULL) { - q_bud = (qq_buddy *) list->data; - if (q_bud != NULL) - old_members[i++] = q_bud->uid; + bd = (qq_buddy_data *) list->data; + if (bd != NULL) + old_members[i++] = bd->uid; list = list->next; } old_members[i] = 0xffffffff; /* this is the end */ @@ -185,21 +176,22 @@ del_members[del] = add_members[add] = 0xffffffff; for (i = 0; i < del; i++) - qq_group_remove_member_by_uid(group, del_members[i]); + qq_room_buddy_remove(rmd, del_members[i]); for (i = 0; i < add; i++) - qq_group_find_or_add_member(gc, group, add_members[i]); + qq_room_buddy_find_or_new(gc, rmd, add_members[i]); if (del > 0) - _qq_group_member_opt(gc, group, QQ_ROOM_MEMBER_DEL, del_members); + _qq_group_member_opt(gc, rmd, QQ_ROOM_MEMBER_DEL, del_members); if (add > 0) - _qq_group_member_opt(gc, group, QQ_ROOM_MEMBER_ADD, add_members); + _qq_group_member_opt(gc, rmd, QQ_ROOM_MEMBER_ADD, add_members); } void qq_group_process_modify_members_reply(guint8 *data, gint len, PurpleConnection *gc) { gint bytes; guint32 id; - qq_group *group; + time_t now = time(NULL); + qq_room_data *rmd; g_return_if_fail(data != NULL); bytes = 0; @@ -207,82 +199,60 @@ g_return_if_fail(id > 0); /* we should have its info locally */ - group = qq_room_search_id(gc, id); - g_return_if_fail(group != NULL); + rmd = qq_room_data_find(gc, id); + g_return_if_fail(rmd != NULL); - purple_debug_info("QQ", "Succeed in modify members for room %d\n", group->ext_id); + purple_debug_info("QQ", "Succeed in modify members for room %d\n", rmd->ext_id); - purple_notify_info(gc, _("QQ Qun Operation"), _("Successed:"), _("Change Qun member")); + qq_room_got_chat_in(gc, id, 0, _("Successed changing Qun member"), now); } -void qq_room_change_info(PurpleConnection *gc, qq_group *group) +void qq_room_change_info(PurpleConnection *gc, qq_room_data *rmd) { - guint8 *data; - gint data_len; + guint8 data[MAX_PACKET_SIZE - 16]; gint bytes; - gchar *group_name, *group_desc, *notice; - g_return_if_fail(group != NULL); + g_return_if_fail(rmd != NULL); - group_name = group->title_utf8 == NULL ? "" : utf8_to_qq(group->title_utf8, QQ_CHARSET_DEFAULT); - group_desc = group->desc_utf8 == NULL ? "" : utf8_to_qq(group->desc_utf8, QQ_CHARSET_DEFAULT); - notice = group->notice_utf8 == NULL ? "" : utf8_to_qq(group->notice_utf8, QQ_CHARSET_DEFAULT); - - data_len = 64 + strlen(group_name) + strlen(group_desc) + strlen(notice); - data = g_newa(guint8, data_len); bytes = 0; /* 005-005 */ bytes += qq_put8(data + bytes, 0x01); /* 006-006 */ - bytes += qq_put8(data + bytes, group->auth_type); + bytes += qq_put8(data + bytes, rmd->auth_type); /* 007-008 */ bytes += qq_put16(data + bytes, 0x0000); /* 009-010 */ - bytes += qq_put16(data + bytes, group->category); + bytes += qq_put16(data + bytes, rmd->category); - bytes += qq_put8(data + bytes, strlen(group_name)); - bytes += qq_putdata(data + bytes, (guint8 *) group_name, strlen(group_name)); + bytes += qq_put_vstr(data + bytes, rmd->title_utf8, QQ_CHARSET_DEFAULT); bytes += qq_put16(data + bytes, 0x0000); - bytes += qq_put8(data + bytes, strlen(notice)); - bytes += qq_putdata(data+ bytes, (guint8 *) notice, strlen(notice)); - - bytes += qq_put8(data + bytes, strlen(group_desc)); - bytes += qq_putdata(data + bytes, (guint8 *) group_desc, strlen(group_desc)); + bytes += qq_put_vstr(data + bytes, rmd->notice_utf8, QQ_CHARSET_DEFAULT); + bytes += qq_put_vstr(data + bytes, rmd->desc_utf8, QQ_CHARSET_DEFAULT); - if (bytes > data_len) { - purple_debug_error("QQ", - "Overflow in qq_room_change_info, max %d bytes, now %d bytes\n", - data_len, bytes); - return; - } - qq_send_room_cmd(gc, QQ_ROOM_CMD_CHANGE_INFO, group->id, data, bytes); + qq_send_room_cmd(gc, QQ_ROOM_CMD_CHANGE_INFO, rmd->id, data, bytes); } void qq_group_process_modify_info_reply(guint8 *data, gint len, PurpleConnection *gc) { gint bytes; guint32 id; - qq_group *group; + time_t now = time(NULL); + g_return_if_fail(data != NULL); bytes = 0; bytes += qq_get32(&id, data + bytes); g_return_if_fail(id > 0); - /* we should have its info locally */ - group = qq_room_search_id(gc, id); - g_return_if_fail(group != NULL); + purple_debug_info("QQ", "Succeed modify room info of %d\n", id); - purple_debug_info("QQ", "Succeed in modify info for Qun %d\n", group->ext_id); - qq_group_refresh(gc, group); - - purple_notify_info(gc, _("QQ Qun Operation"), _("Successed:"), _("Change Qun information")); + qq_room_got_chat_in(gc, id, 0, _("Successed changing Qun information"), now); } -/* we create a very simple group first, and then let the user to modify */ -void qq_room_create_new(PurpleConnection *gc, const gchar *name) +/* we create a very simple room first, and then let the user to modify */ +void qq_create_room(PurpleConnection *gc, const gchar *name) { guint8 *data; gint data_len; @@ -322,25 +292,32 @@ qq_send_room_cmd_noid(gc, QQ_ROOM_CMD_CREATE, data, bytes); } -static void qq_group_setup_with_gc_and_uid(gc_and_uid *g) +static void room_create_cb(qq_room_req *add_req) { - qq_group *group; - g_return_if_fail(g != NULL && g->gc != NULL && g->uid > 0); + qq_room_data *rmd; + g_return_if_fail(add_req != NULL); + if (add_req->gc == NULL || add_req->id == 0) { + g_free(add_req); + return; + } - group = qq_room_search_id(g->gc, g->uid); - g_return_if_fail(group != NULL); + rmd = qq_room_data_find(add_req->gc, add_req->id); + if (rmd == NULL) { + g_free(add_req); + return; + } /* TODO insert UI code here */ - /* qq_group_detail_window_show(g->gc, group); */ - g_free(g); + /* qq_group_detail_window_show(g->gc, rmd); */ + g_free(add_req); } void qq_group_process_create_group_reply(guint8 *data, gint len, PurpleConnection *gc) { gint bytes; guint32 id, ext_id; - qq_group *group; - gc_and_uid *g; + qq_room_data *rmd; + qq_room_req *add_req; qq_data *qd; g_return_if_fail(data != NULL); @@ -352,36 +329,37 @@ bytes += qq_get32(&ext_id, data + bytes); g_return_if_fail(id > 0 && ext_id); - group = qq_group_create_internal_record(gc, id, ext_id, NULL); - group->my_role = QQ_ROOM_ROLE_ADMIN; - group->creator_uid = qd->uid; - qq_group_refresh(gc, group); + qq_room_find_or_new(gc, id, ext_id); + rmd = qq_room_data_find(gc, id); + g_return_if_fail(rmd != NULL); + + rmd->my_role = QQ_ROOM_ROLE_ADMIN; + rmd->creator_uid = qd->uid; qq_send_room_cmd_only(gc, QQ_ROOM_CMD_ACTIVATE, id); - qq_update_room(gc, 0, group->id); + qq_update_room(gc, 0, rmd->id); - purple_debug_info("QQ", "Succeed in create Qun, external ID %d\n", group->ext_id); + purple_debug_info("QQ", "Succeed in create Qun, external ID %d\n", rmd->ext_id); - g = g_new0(gc_and_uid, 1); - g->gc = gc; - g->uid = id; + add_req = g_new0(qq_room_req, 1); + add_req->gc = gc; + add_req->id = id; purple_request_action(gc, _("QQ Qun Operation"), _("You have successfully created a Qun"), - _ - ("Would you like to set up the detail information now?"), + _("Would you like to set up the detail information now?"), 1, purple_connection_get_account(gc), NULL, NULL, - g, 2, - _("Setup"), G_CALLBACK(qq_group_setup_with_gc_and_uid), - _("Cancel"), G_CALLBACK(qq_do_nothing_with_gc_and_uid)); + add_req, 2, + _("Setup"), G_CALLBACK(room_create_cb), + _("Cancel"), G_CALLBACK(room_req_cancel_cb)); } void qq_group_process_activate_group_reply(guint8 *data, gint len, PurpleConnection *gc) { gint bytes; guint32 id; - qq_group *group; + qq_room_data *rmd; g_return_if_fail(data != NULL); bytes = 0; @@ -389,17 +367,17 @@ g_return_if_fail(id > 0); /* we should have its info locally */ - group = qq_room_search_id(gc, id); - g_return_if_fail(group != NULL); + rmd = qq_room_data_find(gc, id); + g_return_if_fail(rmd != NULL); - purple_debug_info("QQ", "Succeed in activate Qun %d\n", group->ext_id); + purple_debug_info("QQ", "Succeed in activate Qun %d\n", rmd->ext_id); } void qq_group_manage_group(PurpleConnection *gc, GHashTable *data) { gchar *id_ptr; guint32 id; - qq_group *group; + qq_room_data *rmd; g_return_if_fail(data != NULL); @@ -407,9 +385,211 @@ id = strtol(id_ptr, NULL, 10); g_return_if_fail(id > 0); - group = qq_room_search_id(gc, id); - g_return_if_fail(group != NULL); + rmd = qq_room_data_find(gc, id); + g_return_if_fail(rmd != NULL); /* XXX insert UI code here */ - /* qq_group_detail_window_show(gc, group); */ + /* qq_group_detail_window_show(gc, rmd); */ +} + +/* receive an application to join the group */ +void qq_process_room_buddy_request_join(guint8 *data, gint len, guint32 id, PurpleConnection *gc) +{ + guint32 ext_id, member_id; + guint8 type8; + gchar *msg, *reason; + qq_room_req *add_req; + gchar *who; + gint bytes = 0; + qq_room_data *rmd; + time_t now = time(NULL); + + g_return_if_fail(id > 0 && data != NULL && len > 0); + + /* FIXME: check length here */ + + bytes += qq_get32(&ext_id, data + bytes); + bytes += qq_get8(&type8, data + bytes); + bytes += qq_get32(&member_id, data + bytes); + + g_return_if_fail(ext_id > 0 && member_id > 0); + + bytes += qq_get_vstr(&reason, QQ_CHARSET_DEFAULT, data + bytes); + + add_req = g_new0(qq_room_req, 1); + add_req->gc = gc; + add_req->id = id; + add_req->member = member_id; + + purple_debug_info("QQ", "%d requested to join room, ext id %d\n", member_id, ext_id); + + rmd = qq_room_data_find(gc, id); + g_return_if_fail(rmd != NULL); + if (qq_room_buddy_find(rmd, member_id)) { + purple_debug_info("QQ", "Approve join, buddy joined before\n"); + msg = g_strdup_printf(_("%d requested to join Qun %d for %s"), + member_id, ext_id, reason); + qq_room_got_chat_in(gc, id, 0, msg, now); + qq_send_cmd_group_auth(gc, rmd, QQ_ROOM_AUTH_REQUEST_APPROVE, member_id, ""); + g_free(msg); + g_free(reason); + return; + } + + if (purple_prefs_get_bool("/plugins/prpl/qq/auto_get_authorize_info")) { + qq_request_buddy_info(gc, member_id, 0, QQ_BUDDY_INFO_DISPLAY); + } + who = uid_to_purple_name(member_id); + msg = g_strdup_printf(_("%d request to join Qun %d"), member_id, ext_id); + + purple_request_action(gc, _("QQ Qun Operation"), + msg, reason, + PURPLE_DEFAULT_ACTION_NONE, + purple_connection_get_account(gc), who, NULL, + add_req, 2, + _("Deny"), G_CALLBACK(member_join_deny_cb), + _("Authorize"), G_CALLBACK(member_join_authorize_cb)); + + g_free(who); + g_free(msg); + g_free(reason); + g_free(reason); +} + +/* the request to join a group is rejected */ +void qq_process_room_buddy_rejected(guint8 *data, gint len, guint32 id, PurpleConnection *gc) +{ + guint32 ext_id, admin_uid; + guint8 type8; + gchar *msg, *reason; + qq_room_data *rmd; + gint bytes; + + g_return_if_fail(data != NULL && len > 0); + + /* FIXME: check length here */ + bytes = 0; + bytes += qq_get32(&ext_id, data + bytes); + bytes += qq_get8(&type8, data + bytes); + bytes += qq_get32(&admin_uid, data + bytes); + + g_return_if_fail(ext_id > 0 && admin_uid > 0); + + bytes += qq_get_vstr(&reason, QQ_CHARSET_DEFAULT, data + bytes); + + msg = g_strdup_printf + (_("Failed to join Qun %d, operated by admin %d"), ext_id, admin_uid); + + purple_notify_warning(gc, _("QQ Qun Operation"), msg, reason); + + qq_room_find_or_new(gc, id, ext_id); + rmd = qq_room_data_find(gc, id); + if (rmd != NULL) { + rmd->my_role = QQ_ROOM_ROLE_NO; + } + + g_free(msg); + g_free(reason); } + +/* the request to join a group is approved */ +void qq_process_room_buddy_approved(guint8 *data, gint len, guint32 id, PurpleConnection *gc) +{ + guint32 ext_id, admin_uid; + guint8 type8; + gchar *msg, *reason; + qq_room_data *rmd; + gint bytes; + time_t now; + + g_return_if_fail(data != NULL && len > 0); + + /* FIXME: check length here */ + bytes = 0; + bytes += qq_get32(&ext_id, data + bytes); + bytes += qq_get8(&type8, data + bytes); + bytes += qq_get32(&admin_uid, data + bytes); + + g_return_if_fail(ext_id > 0 && admin_uid > 0); + /* it is also a "无" here, so do not display */ + bytes += qq_get_vstr(&reason, QQ_CHARSET_DEFAULT, data + bytes); + + qq_room_find_or_new(gc, id, ext_id); + rmd = qq_room_data_find(gc, id); + if (rmd != NULL) { + rmd->my_role = QQ_ROOM_ROLE_YES; + } + + msg = g_strdup_printf(_("Joinning Qun %d is approved by Admin %d for %s"), + ext_id, admin_uid, reason); + now = time(NULL); + qq_room_got_chat_in(gc, id, 0, msg, now); + + g_free(msg); + g_free(reason); +} + +/* process the packet when removed from a group */ +void qq_process_room_buddy_removed(guint8 *data, gint len, guint32 id, PurpleConnection *gc) +{ + guint32 ext_id, uid; + guint8 type8; + gchar *msg; + qq_room_data *rmd; + gint bytes = 0; + time_t now = time(NULL); + + g_return_if_fail(data != NULL && len > 0); + + /* FIXME: check length here */ + bytes = 0; + bytes += qq_get32(&ext_id, data + bytes); + bytes += qq_get8(&type8, data + bytes); + bytes += qq_get32(&uid, data + bytes); + + g_return_if_fail(ext_id > 0 && uid > 0); + + qq_room_find_or_new(gc, id, ext_id); + rmd = qq_room_data_find(gc, id); + if (rmd != NULL) { + rmd->my_role = QQ_ROOM_ROLE_NO; + } + + msg = g_strdup_printf(_("Removed buddy %d."), uid); + qq_room_got_chat_in(gc, id, 0, msg, now); + g_free(msg); +} + +/* process the packet when added to a group */ +void qq_process_room_buddy_joined(guint8 *data, gint len, guint32 id, PurpleConnection *gc) +{ + guint32 ext_id, uid; + guint8 type8; + qq_room_data *rmd; + gint bytes; + gchar *msg; + time_t now = time(NULL); + + g_return_if_fail(data != NULL && len > 0); + + /* FIXME: check length here */ + bytes = 0; + bytes += qq_get32(&ext_id, data + bytes); + bytes += qq_get8(&type8, data + bytes); + bytes += qq_get32(&uid, data + bytes); + + g_return_if_fail(ext_id > 0 && id > 0); + + qq_room_find_or_new(gc, id, ext_id); + rmd = qq_room_data_find(gc, id); + g_return_if_fail(rmd != NULL); + + rmd->my_role = QQ_ROOM_ROLE_YES; + + qq_update_room(gc, 0, rmd->id); + + msg = g_strdup_printf(_("New buddy %d joined."), uid); + qq_room_got_chat_in(gc, id, 0, msg, now); + g_free(msg); +} + diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group_opt.h --- a/libpurple/protocols/qq/group_opt.h Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/group_opt.h Wed Oct 29 21:38:53 2008 +0000 @@ -31,11 +31,11 @@ #define QQ_QUN_MEMBER_MAX 80 /* max number of the group */ -typedef struct _group_member_opt { +typedef struct _qq_room_req { PurpleConnection *gc; guint32 id; guint32 member; -} group_member_opt; +} qq_room_req; enum { QQ_ROOM_TYPE_PERMANENT = 0x01, @@ -47,18 +47,19 @@ QQ_ROOM_MEMBER_DEL }; -void qq_group_modify_members(PurpleConnection *gc, qq_group *group, guint32 *new_members); -void qq_room_change_info(PurpleConnection *gc, qq_group *group); +void qq_group_modify_members(PurpleConnection *gc, qq_room_data *rmd, guint32 *new_members); +void qq_room_change_info(PurpleConnection *gc, qq_room_data *rmd); -void qq_group_approve_application_with_struct(group_member_opt *g); -void qq_group_reject_application_with_struct(group_member_opt *g); -void qq_group_search_application_with_struct(group_member_opt *g); - +void qq_create_room(PurpleConnection *gc, const gchar *name); void qq_group_process_modify_info_reply(guint8 *data, gint len, PurpleConnection *gc); void qq_group_process_modify_members_reply(guint8 *data, gint len, PurpleConnection *gc); void qq_group_manage_group(PurpleConnection *gc, GHashTable *data); -void qq_room_create_new(PurpleConnection *gc, const gchar *name); void qq_group_process_activate_group_reply(guint8 *data, gint len, PurpleConnection *gc); void qq_group_process_create_group_reply(guint8 *data, gint len, PurpleConnection *gc); +void qq_process_room_buddy_request_join(guint8 *data, gint len, guint32 id, PurpleConnection *gc); +void qq_process_room_buddy_rejected(guint8 *data, gint len, guint32 id, PurpleConnection *gc); +void qq_process_room_buddy_approved(guint8 *data, gint len, guint32 id, PurpleConnection *gc); +void qq_process_room_buddy_removed(guint8 *data, gint len, guint32 id, PurpleConnection *gc); +void qq_process_room_buddy_joined(guint8 *data, gint len, guint32 id, PurpleConnection *gc); #endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group_search.c --- a/libpurple/protocols/qq/group_search.c Mon Oct 27 23:13:11 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,133 +0,0 @@ -/** - * @file group_search.c - * - * purple - * - * Purple 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ - -#include "internal.h" - -#include "debug.h" - -#include "char_conv.h" -#include "group_find.h" -#include "group_free.h" -#include "group_internal.h" -#include "group_join.h" -#include "group_search.h" -#include "utils.h" -#include "header_info.h" -#include "packet_parse.h" -#include "qq_network.h" - -enum { - QQ_ROOM_SEARCH_TYPE_BY_ID = 0x01, - QQ_ROOM_SEARCH_TYPE_DEMO = 0x02 -}; - -/* send packet to search for qq_group */ -void qq_send_cmd_group_search_group(PurpleConnection *gc, guint32 ext_id) -{ - guint8 raw_data[16] = {0}; - gint bytes = 0; - guint8 type; - - type = (ext_id == 0x00000000) ? QQ_ROOM_SEARCH_TYPE_DEMO : QQ_ROOM_SEARCH_TYPE_BY_ID; - - bytes = 0; - bytes += qq_put8(raw_data + bytes, type); - bytes += qq_put32(raw_data + bytes, ext_id); - - qq_send_room_cmd_noid(gc, QQ_ROOM_CMD_SEARCH, raw_data, bytes); -} - -static void _qq_setup_roomlist(qq_data *qd, qq_group *group) -{ - PurpleRoomlistRoom *room; - gchar field[11]; - - room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, group->title_utf8, NULL); - g_snprintf(field, sizeof(field), "%d", group->ext_id); - purple_roomlist_room_add_field(qd->roomlist, room, field); - g_snprintf(field, sizeof(field), "%d", group->creator_uid); - purple_roomlist_room_add_field(qd->roomlist, room, field); - purple_roomlist_room_add_field(qd->roomlist, room, group->desc_utf8); - g_snprintf(field, sizeof(field), "%d", group->id); - purple_roomlist_room_add_field(qd->roomlist, room, field); - g_snprintf(field, sizeof(field), "%d", group->type8); - purple_roomlist_room_add_field(qd->roomlist, room, field); - g_snprintf(field, sizeof(field), "%d", group->auth_type); - purple_roomlist_room_add_field(qd->roomlist, room, field); - g_snprintf(field, sizeof(field), "%d", group->category); - purple_roomlist_room_add_field(qd->roomlist, room, field); - purple_roomlist_room_add_field(qd->roomlist, room, group->title_utf8); - purple_roomlist_room_add(qd->roomlist, room); - - purple_roomlist_set_in_progress(qd->roomlist, FALSE); -} - -/* process group cmd reply "search group" */ -void qq_process_group_cmd_search_group(guint8 *data, gint len, PurpleConnection *gc) -{ - gint bytes; - guint8 search_type; - guint16 unknown; - qq_group group; - qq_data *qd; - GSList *pending_id; - - g_return_if_fail(data != NULL && len > 0); - qd = (qq_data *) gc->proto_data; - - bytes = 0; - bytes += qq_get8(&search_type, data + bytes); - - /* now it starts with group_info_entry */ - bytes += qq_get32(&(group.id), data + bytes); - bytes += qq_get32(&(group.ext_id), data + bytes); - bytes += qq_get8(&(group.type8), data + bytes); - bytes += qq_get16(&(unknown), data + bytes); - bytes += qq_get16(&(unknown), data + bytes); - bytes += qq_get32(&(group.creator_uid), data + bytes); - bytes += qq_get16(&(unknown), data + bytes); - bytes += qq_get16(&(unknown), data + bytes); - bytes += qq_get16(&(unknown), data + bytes); - bytes += qq_get32(&(group.category), data + bytes); - bytes += convert_as_pascal_string(data + bytes, &(group.title_utf8), QQ_CHARSET_DEFAULT); - bytes += qq_get16(&(unknown), data + bytes); - bytes += qq_get8(&(group.auth_type), data + bytes); - bytes += convert_as_pascal_string(data + bytes, &(group.desc_utf8), QQ_CHARSET_DEFAULT); - /* end of one qq_group */ - if(bytes != len) { - purple_debug_error("QQ", - "group_cmd_search_group: Dangerous error! maybe protocol changed, notify developers!"); - } - - pending_id = qq_get_pending_id(qd->joining_groups, group.ext_id); - if (pending_id != NULL) { - qq_set_pending_id(&qd->joining_groups, group.ext_id, FALSE); - if (qq_room_search_id(gc, group.id) == NULL) - qq_group_create_internal_record(gc, - group.id, group.ext_id, group.title_utf8); - qq_request_room_join(gc, &group); - } else { - _qq_setup_roomlist(qd, &group); - } -} diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/group_search.h --- a/libpurple/protocols/qq/group_search.h Mon Oct 27 23:13:11 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -/** - * @file group_search.h - * - * purple - * - * Purple 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ - -#ifndef _QQ_GROUP_SEARCH_H_ -#define _QQ_GROUP_SEARCH_H_ - -#include -#include "connection.h" - -void qq_send_cmd_group_search_group(PurpleConnection *gc, guint32 external_group_id); -void qq_process_group_cmd_search_group(guint8 *data, gint len, PurpleConnection *gc); - -#endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/header_info.c --- a/libpurple/protocols/qq/header_info.c Mon Oct 27 23:13:11 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,236 +0,0 @@ -/** - * @file header_info.c - * - * purple - * - * Purple 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ - -#include "internal.h" - -#include "header_info.h" - -#define QQ_CLIENT_062E 0x062e /* GB QQ2000c build 0630 */ -#define QQ_CLIENT_072E 0x072e /* EN QQ2000c build 0305 */ -#define QQ_CLIENT_0801 0x0801 /* EN QQ2000c build 0630 */ -#define QQ_CLIENT_0A1D 0x0a1d /* GB QQ2003c build 0808 */ -#define QQ_CLIENT_0B07 0x0b07 /* GB QQ2003c build 0925 */ -#define QQ_CLIENT_0B2F 0x0b2f /* GB QQ2003iii build 0117 */ -#define QQ_CLIENT_0B35 0x0b35 /* GB QQ2003iii build 0304 (offical release) */ -#define QQ_CLIENT_0B37 0x0b37 /* GB QQ2003iii build 0304 (April 05 updates) */ -#define QQ_CLIENT_0E1B 0x0e1b /* QQ2005 ? */ -#define QQ_CLIENT_0E35 0x0e35 /* EN QQ2005 V05.0.200.020 */ -#define QQ_CLIENT_0F15 0x0f15 /* QQ2006 Spring Festival build */ -#define QQ_CLIENT_0F5F 0x0f5f /* QQ2006 final build */ - -#define QQ_CLIENT_0C0B 0x0C0B /* QQ2004 */ -#define QQ_CLIENT_0C0D 0x0C0D /* QQ2004 preview*/ -#define QQ_CLIENT_0C21 0x0C21 /* QQ2004 */ -#define QQ_CLIENT_0C49 0x0C49 /* QQ2004II */ -#define QQ_CLIENT_0D05 0x0D05 /* QQ2005 beta1 */ -#define QQ_CLIENT_0D51 0x0D51 /* QQ2005 beta2 */ -#define QQ_CLIENT_0D61 0x0D61 /* QQ2005 */ -#define QQ_CLIENT_05A5 0x05A5 /* ? */ -#define QQ_CLIENT_05F1 0x0F15 /* QQ2006 Spring Festival */ -#define QQ_CLIENT_0F4B 0x0F4B /* QQ2006 Beta 3 */ - -#define QQ_CLIENT_1105 0x1105 /* QQ2007 beta4*/ -#define QQ_CLIENT_111D 0x111D /* QQ2007 */ -#define QQ_CLIENT_115B 0x115B /* QQ2008 */ -#define QQ_CLIENT_1203 0x1203 /* QQ2008 */ -#define QQ_CLIENT_1205 0x1205 /* QQ2008 */ -#define QQ_CLIENT_120B 0x120B /* QQ2008 July 8.0.978.400 */ -#define QQ_CLIENT_1412 0x1412 /* QQMac 1.0 preview1 build 670 */ -#define QQ_CLIENT_1441 0x1441 /* QQ2009 preview2 */ - -#define QQ_SERVER_0100 0x0100 /* server */ - - -/* given source tag, return its description accordingly */ -const gchar *qq_get_ver_desc(gint source) -{ - switch (source) { - case QQ_CLIENT_062E: - return "GB QQ2000c build 0630"; - case QQ_CLIENT_072E: - return "En QQ2000c build 0305"; - case QQ_CLIENT_0801: - return "En QQ2000c build 0630"; - case QQ_CLIENT_0A1D: - return "GB QQ2003ii build 0808"; - case QQ_CLIENT_0B07: - return "GB QQ2003ii build 0925"; - case QQ_CLIENT_0B2F: - return "GB QQ2003iii build 0117"; - case QQ_CLIENT_0B35: - return "GB QQ2003iii build 0304"; - case QQ_CLIENT_0B37: - return "GB QQ2003iii build 0304 (April 5 update)"; - case QQ_CLIENT_0C0B: - return "QQ2004"; - case QQ_CLIENT_0C0D: - return "QQ2004 preview"; - case QQ_CLIENT_0C21: - return "QQ2004"; - case QQ_CLIENT_0C49: - return "QQ2004II"; - case QQ_CLIENT_0D05: - return "QQ2005 beta1"; - case QQ_CLIENT_0D51: - return "QQ2005 beta2"; - case QQ_CLIENT_0D61: - return "QQ2005"; - case QQ_CLIENT_0E1B: - return "QQ2005 or QQ2006"; - case QQ_CLIENT_0E35: - return "En QQ2005 V05.0.200.020"; - case QQ_CLIENT_0F15: - return "QQ2006 Spring Festival"; - case QQ_CLIENT_0F4B: - return "QQ2006 beta3"; - case QQ_CLIENT_0F5F: - return "QQ2006 final build"; - case QQ_CLIENT_1105: - return "QQ2007 beta4"; - case QQ_CLIENT_111D: - return "QQ2007"; - case QQ_CLIENT_115B: - case QQ_CLIENT_1203: - case QQ_CLIENT_1205: - case QQ_CLIENT_120B: - return "QQ2008"; - case QQ_CLIENT_1412: - return "QQMac 1.0 preview1 build 670"; - case QQ_CLIENT_1441: - return "QQ2009 preview2"; - case QQ_SERVER_0100: - return "QQ Server 0100"; - default: - return "Unknown Version"; - } -} - -/* given command alias, return the command name accordingly */ -const gchar *qq_get_cmd_desc(gint cmd) -{ - switch (cmd) { - case QQ_CMD_LOGOUT: - return "QQ_CMD_LOGOUT"; - case QQ_CMD_KEEP_ALIVE: - return "QQ_CMD_KEEP_ALIVE"; - case QQ_CMD_UPDATE_INFO: - return "QQ_CMD_UPDATE_INFO"; - case QQ_CMD_SEARCH_USER: - return "QQ_CMD_SEARCH_USER"; - case QQ_CMD_GET_BUDDY_INFO: - return "QQ_CMD_GET_BUDDY_INFO"; - case QQ_CMD_ADD_BUDDY_WO_AUTH: - return "QQ_CMD_ADD_BUDDY_WO_AUTH"; - case QQ_CMD_DEL_BUDDY: - return "QQ_CMD_DEL_BUDDY"; - case QQ_CMD_BUDDY_AUTH: - return "QQ_CMD_BUDDY_AUTH"; - case QQ_CMD_CHANGE_STATUS: - return "QQ_CMD_CHANGE_STATUS"; - case QQ_CMD_ACK_SYS_MSG: - return "QQ_CMD_ACK_SYS_MSG"; - case QQ_CMD_SEND_IM: - return "QQ_CMD_SEND_IM"; - case QQ_CMD_RECV_IM: - return "QQ_CMD_RECV_IM"; - case QQ_CMD_REMOVE_SELF: - return "QQ_CMD_REMOVE_SELF"; - case QQ_CMD_LOGIN: - return "QQ_CMD_LOGIN"; - case QQ_CMD_GET_BUDDIES_LIST: - return "QQ_CMD_GET_BUDDIES_LIST"; - case QQ_CMD_GET_BUDDIES_ONLINE: - return "QQ_CMD_GET_BUDDIES_ONLINE"; - case QQ_CMD_ROOM: - return "QQ_CMD_ROOM"; - case QQ_CMD_GET_BUDDIES_AND_ROOMS: - return "QQ_CMD_GET_BUDDIES_AND_ROOMS"; - case QQ_CMD_GET_LEVEL: - return "QQ_CMD_GET_LEVEL"; - case QQ_CMD_TOKEN: - return "QQ_CMD_TOKEN"; - case QQ_CMD_RECV_MSG_SYS: - return "QQ_CMD_RECV_MSG_SYS"; - case QQ_CMD_BUDDY_CHANGE_STATUS: - return "QQ_CMD_BUDDY_CHANGE_STATUS"; - default: - return "Unknown CMD"; - } -} - -const gchar *qq_get_room_cmd_desc(gint room_cmd) -{ - switch (room_cmd) { - case QQ_ROOM_CMD_CREATE: - return "QQ_ROOM_CMD_CREATE"; - case QQ_ROOM_CMD_MEMBER_OPT: - return "QQ_ROOM_CMD_MEMBER_OPT"; - case QQ_ROOM_CMD_CHANGE_INFO: - return "QQ_ROOM_CMD_CHANGE_INFO"; - case QQ_ROOM_CMD_GET_INFO: - return "QQ_ROOM_CMD_GET_INFO"; - case QQ_ROOM_CMD_ACTIVATE: - return "QQ_ROOM_CMD_ACTIVATE"; - case QQ_ROOM_CMD_SEARCH: - return "QQ_ROOM_CMD_SEARCH"; - case QQ_ROOM_CMD_JOIN: - return "QQ_ROOM_CMD_JOIN"; - case QQ_ROOM_CMD_AUTH: - return "QQ_ROOM_CMD_AUTH"; - case QQ_ROOM_CMD_QUIT: - return "QQ_ROOM_CMD_QUIT"; - case QQ_ROOM_CMD_SEND_MSG: - return "QQ_ROOM_CMD_SEND_MSG"; - case QQ_ROOM_CMD_GET_ONLINES: - return "QQ_ROOM_CMD_GET_ONLINES"; - case QQ_ROOM_CMD_GET_BUDDIES: - return "QQ_ROOM_CMD_GET_BUDDIES"; - case QQ_ROOM_CMD_CHANGE_CARD: - return "QQ_ROOM_CMD_CHANGE_CARD"; - case QQ_ROOM_CMD_GET_REALNAMES: - return "QQ_ROOM_CMD_GET_REALNAMES"; - case QQ_ROOM_CMD_GET_CARD: - return "QQ_ROOM_CMD_GET_CARD"; - case QQ_ROOM_CMD_SEND_IM_EX: - return "QQ_ROOM_CMD_SEND_IM_EX"; - case QQ_ROOM_CMD_ADMIN: - return "QQ_ROOM_CMD_ADMIN"; - case QQ_ROOM_CMD_TRANSFER: - return "QQ_ROOM_CMD_TRANSFER"; - case QQ_ROOM_CMD_TEMP_CREATE: - return "QQ_ROOM_CMD_TEMP_CREATE"; - case QQ_ROOM_CMD_TEMP_CHANGE_MEMBER: - return "QQ_ROOM_CMD_TEMP_CHANGE_MEMBER"; - case QQ_ROOM_CMD_TEMP_QUIT: - return "QQ_ROOM_CMD_TEMP_QUIT"; - case QQ_ROOM_CMD_TEMP_GET_INFO: - return "QQ_ROOM_CMD_TEMP_GET_INFO"; - case QQ_ROOM_CMD_TEMP_SEND_IM: - return "QQ_ROOM_CMD_TEMP_SEND_IM"; - case QQ_ROOM_CMD_TEMP_GET_MEMBERS: - return "QQ_ROOM_CMD_TEMP_GET_MEMBERS"; - default: - return "Unknown Room Command"; - } -} diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/header_info.h --- a/libpurple/protocols/qq/header_info.h Mon Oct 27 23:13:11 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,101 +0,0 @@ -/** - * @file header_info.h - * - * purple - * - * Purple 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ - -#ifndef _QQ_HEADER_INFO_H_ -#define _QQ_HEADER_INFO_H_ - -#include - -#define QQ_UDP_HEADER_LENGTH 7 -#define QQ_TCP_HEADER_LENGTH 9 - -#define QQ_PACKET_TAG 0x02 /* all QQ text packets starts with it */ -#define QQ_PACKET_TAIL 0x03 /* all QQ text packets end with it */ - -#define QQ_CLIENT 0x0d55 - -const gchar *qq_get_ver_desc(gint source); - -/* list of known QQ commands */ -enum { - QQ_CMD_LOGOUT = 0x0001, /* log out */ - QQ_CMD_KEEP_ALIVE = 0x0002, /* get onlines from tencent */ - QQ_CMD_UPDATE_INFO = 0x0004, /* update information */ - QQ_CMD_SEARCH_USER = 0x0005, /* search for user */ - QQ_CMD_GET_BUDDY_INFO = 0x0006, /* get user information */ - QQ_CMD_ADD_BUDDY_WO_AUTH = 0x0009, /* add buddy without auth */ - QQ_CMD_DEL_BUDDY = 0x000a, /* delete a buddy */ - QQ_CMD_BUDDY_AUTH = 0x000b, /* buddy authentication */ - QQ_CMD_CHANGE_STATUS = 0x000d, /* change my online status */ - QQ_CMD_ACK_SYS_MSG = 0x0012, /* ack system message */ - QQ_CMD_SEND_IM = 0x0016, /* send message */ - QQ_CMD_RECV_IM = 0x0017, /* receive message */ - QQ_CMD_REMOVE_SELF = 0x001c, /* remove self */ - QQ_CMD_REQUEST_KEY = 0x001d, /* request key for file transfer */ - QQ_CMD_CELL_PHONE_1 = 0x0021, /* cell phone 1 */ - QQ_CMD_LOGIN = 0x0022, /* login */ - QQ_CMD_GET_BUDDIES_LIST = 0x0026, /* get buddies list */ - QQ_CMD_GET_BUDDIES_ONLINE = 0x0027, /* get online buddies list */ - QQ_CMD_CELL_PHONE_2 = 0x0029, /* cell phone 2 */ - QQ_CMD_ROOM = 0x0030, /* room command */ - QQ_CMD_GET_BUDDIES_AND_ROOMS = 0x0058, - QQ_CMD_GET_LEVEL = 0x005C, /* get level for one or more buddies */ - QQ_CMD_TOKEN = 0x0062, /* get login token */ - QQ_CMD_RECV_MSG_SYS = 0x0080, /* receive a system message */ - QQ_CMD_BUDDY_CHANGE_STATUS = 0x0081, /* buddy change status */ -}; - -const gchar *qq_get_cmd_desc(gint type); - -enum { - QQ_ROOM_CMD_CREATE = 0x01, - QQ_ROOM_CMD_MEMBER_OPT = 0x02, - QQ_ROOM_CMD_CHANGE_INFO = 0x03, - QQ_ROOM_CMD_GET_INFO = 0x04, - QQ_ROOM_CMD_ACTIVATE = 0x05, - QQ_ROOM_CMD_SEARCH = 0x06, - QQ_ROOM_CMD_JOIN = 0x07, - QQ_ROOM_CMD_AUTH = 0x08, - QQ_ROOM_CMD_QUIT = 0x09, - QQ_ROOM_CMD_SEND_MSG = 0x0a, - QQ_ROOM_CMD_GET_ONLINES = 0x0b, - QQ_ROOM_CMD_GET_BUDDIES = 0x0c, - - QQ_ROOM_CMD_CHANGE_CARD = 0x0E, - QQ_ROOM_CMD_GET_REALNAMES = 0x0F, - QQ_ROOM_CMD_GET_CARD = 0x10, - QQ_ROOM_CMD_SEND_IM_EX = 0x1A, - QQ_ROOM_CMD_ADMIN = 0x1B, - QQ_ROOM_CMD_TRANSFER = 0x1C, - QQ_ROOM_CMD_TEMP_CREATE = 0x30, - QQ_ROOM_CMD_TEMP_CHANGE_MEMBER = 0x31, - QQ_ROOM_CMD_TEMP_QUIT = 0x32, - QQ_ROOM_CMD_TEMP_GET_INFO = 0x33, - QQ_ROOM_CMD_TEMP_SEND_IM = 0x35, - QQ_ROOM_CMD_TEMP_GET_MEMBERS = 0x37, -}; - -const gchar *qq_get_room_cmd_desc(gint room_cmd); - -#endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/im.c --- a/libpurple/protocols/qq/im.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/im.c Wed Oct 29 21:38:53 2008 +0000 @@ -35,15 +35,13 @@ #include "buddy_list.h" #include "buddy_opt.h" #include "char_conv.h" -#include "group_im.h" -#include "header_info.h" +#include "qq_define.h" #include "im.h" #include "packet_parse.h" #include "qq_network.h" #include "send_file.h" #include "utils.h" -#define QQ_SEND_IM_REPLY_OK 0x00 #define DEFAULT_FONT_NAME_LEN 4 enum @@ -64,52 +62,15 @@ QQ_NORMAL_IM_FILE_EX_NOTIFY_IP = 0x87 }; -enum { - QQ_RECV_SYS_IM_KICK_OUT = 0x01 -}; - -typedef struct _qq_recv_im_header qq_recv_im_header; -typedef struct _qq_recv_normal_im_text qq_recv_normal_im_text; -typedef struct _qq_recv_normal_im_common qq_recv_normal_im_common; -typedef struct _qq_recv_normal_im_unprocessed qq_recv_normal_im_unprocessed; - -struct _qq_recv_normal_im_common { - /* this is the common part of normal_text */ - guint16 sender_ver; - guint32 sender_uid; - guint32 receiver_uid; - guint8 session_md5[QQ_KEY_LENGTH]; - guint16 normal_im_type; -}; +typedef struct _qq_im_header qq_im_header; +typedef struct _qq_recv_extended_im_text qq_recv_extended_im_text; -struct _qq_recv_normal_im_text { - qq_recv_normal_im_common *common; - /* now comes the part for text only */ - guint16 msg_seq; - guint32 send_time; - guint16 sender_icon; - guint8 unknown2[3]; - guint8 is_there_font_attr; - guint8 unknown3[4]; - guint8 msg_type; - gchar *msg; /* no fixed length, ends with 0x00 */ - guint8 *font_attr; - gint font_attr_len; -}; - -struct _qq_recv_normal_im_unprocessed { - qq_recv_normal_im_common *common; - /* now comes the part of unprocessed */ - guint8 *unknown; /* no fixed length */ - gint length; -}; - -struct _qq_recv_im_header { - guint32 sender_uid; - guint32 receiver_uid; - guint32 server_im_seq; - struct in_addr sender_ip; - guint16 sender_port; +struct _qq_im_header { + /* this is the common part of normal_text */ + guint16 version_from; + guint32 uid_from; + guint32 uid_to; + guint8 session_md5[QQ_KEY_LENGTH]; guint16 im_type; }; @@ -182,327 +143,360 @@ return (guint8 *) send_im_tail; } -static const gchar *qq_get_recv_im_type_str(gint type) -{ - switch (type) { - case QQ_RECV_IM_TO_BUDDY: - return "QQ_RECV_IM_TO_BUDDY"; - case QQ_RECV_IM_TO_UNKNOWN: - return "QQ_RECV_IM_TO_UNKNOWN"; - case QQ_RECV_IM_UNKNOWN_QUN_IM: - return "QQ_RECV_IM_UNKNOWN_QUN_IM"; - case QQ_RECV_IM_ADD_TO_QUN: - return "QQ_RECV_IM_ADD_TO_QUN"; - case QQ_RECV_IM_DEL_FROM_QUN: - return "QQ_RECV_IM_DEL_FROM_QUN"; - case QQ_RECV_IM_APPLY_ADD_TO_QUN: - return "QQ_RECV_IM_APPLY_ADD_TO_QUN"; - case QQ_RECV_IM_CREATE_QUN: - return "QQ_RECV_IM_CREATE_QUN"; - case QQ_RECV_IM_SYS_NOTIFICATION: - return "QQ_RECV_IM_SYS_NOTIFICATION"; - case QQ_RECV_IM_APPROVE_APPLY_ADD_TO_QUN: - return "QQ_RECV_IM_APPROVE_APPLY_ADD_TO_QUN"; - case QQ_RECV_IM_REJCT_APPLY_ADD_TO_QUN: - return "QQ_RECV_IM_REJCT_APPLY_ADD_TO_QUN"; - case QQ_RECV_IM_TEMP_QUN_IM: - return "QQ_RECV_IM_TEMP_QUN_IM"; - case QQ_RECV_IM_QUN_IM: - return "QQ_RECV_IM_QUN_IM"; - case QQ_RECV_IM_NEWS: - return "QQ_RECV_IM_NEWS"; - case QQ_RECV_IM_FROM_BUDDY_2006: - return "QQ_RECV_IM_FROM_BUDDY_2006"; - case QQ_RECV_IM_FROM_UNKNOWN_2006: - return "QQ_RECV_IM_FROM_UNKNOWN_2006"; - default: - return "QQ_RECV_IM_UNKNOWN"; - } -} - /* read the common parts of the normal_im, * returns the bytes read if succeed, or -1 if there is any error */ -static gint _qq_normal_im_common_read(guint8 *data, gint len, qq_recv_normal_im_common *common) +static gint get_im_header(qq_im_header *im_header, guint8 *data, gint len) { gint bytes; - g_return_val_if_fail(data != NULL && len != 0 && common != NULL, -1); + g_return_val_if_fail(data != NULL && len > 0, -1); bytes = 0; - /* now push data into common header */ - bytes += qq_get16(&(common->sender_ver), data + bytes); - bytes += qq_get32(&(common->sender_uid), data + bytes); - bytes += qq_get32(&(common->receiver_uid), data + bytes); - bytes += qq_getdata(common->session_md5, QQ_KEY_LENGTH, data + bytes); - bytes += qq_get16(&(common->normal_im_type), data + bytes); - - if (bytes != 28) { /* read common place fail */ - purple_debug_error("QQ", "Expect 28 bytes, read %d bytes\n", bytes); - return -1; - } - + bytes += qq_get16(&(im_header->version_from), data + bytes); + bytes += qq_get32(&(im_header->uid_from), data + bytes); + bytes += qq_get32(&(im_header->uid_to), data + bytes); + bytes += qq_getdata(im_header->session_md5, QQ_KEY_LENGTH, data + bytes); + bytes += qq_get16(&(im_header->im_type), data + bytes); return bytes; } -static void _qq_process_recv_news(guint8 *data, gint data_len, PurpleConnection *gc) +void qq_got_attention(PurpleConnection *gc, const gchar *msg) { - qq_data *qd = (qq_data *) gc->proto_data; - gint bytes; - guint8 *temp; - guint8 temp_len; - gchar *title, *brief, *url; - gchar *title_utf8; - gchar *content, *content_utf8; - - g_return_if_fail(data != NULL && data_len != 0); - -#if 0 - qq_show_packet("Rcv news", data, data_len); -#endif - - temp = g_newa(guint8, data_len); - bytes = 4; /* ignore unknown 4 bytes */ - - bytes += qq_get8(&temp_len, data + bytes); - g_return_if_fail(bytes + temp_len <= data_len); - bytes += qq_getdata(temp, temp_len, data+bytes); - title = g_strndup((gchar *)temp, temp_len); + qq_data *qd; + gchar *from; + time_t now = time(NULL); - bytes += qq_get8(&temp_len, data + bytes); - g_return_if_fail(bytes + temp_len <= data_len); - bytes += qq_getdata(temp, temp_len, data+bytes); - brief = g_strndup((gchar *)temp, temp_len); + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = gc->proto_data; - bytes += qq_get8(&temp_len, data + bytes); - g_return_if_fail(bytes + temp_len <= data_len); - bytes += qq_getdata(temp, temp_len, data+bytes); - url = g_strndup((gchar *)temp, temp_len); + g_return_if_fail(qd->uid > 0); - title_utf8 = qq_to_utf8(title, QQ_CHARSET_DEFAULT); - content = g_strdup_printf(_("%s\n\n%s"), brief, url); - content_utf8 = qq_to_utf8(content, QQ_CHARSET_DEFAULT); + qq_buddy_find_or_new(gc, qd->uid); - if (qd->is_show_news) { - purple_notify_info(gc, _("QQ Server News"), title_utf8, content_utf8); - } else { - purple_debug_info("QQ", "QQ Server news:\n%s\n%s", title_utf8, content_utf8); - } - g_free(title); - g_free(title_utf8); - g_free(brief); - g_free(url); - g_free(content); - g_free(content_utf8); + from = uid_to_purple_name(qd->uid); + serv_got_im(gc, from, msg, PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NOTIFY, now); + g_free(from); } /* process received normal text IM */ -static void _qq_process_recv_normal_im_text(guint8 *data, gint len, qq_recv_normal_im_common *common, PurpleConnection *gc) +static void process_im_text(PurpleConnection *gc, guint8 *data, gint len, qq_im_header *im_header) { guint16 purple_msg_type; - gchar *name; + gchar *who; gchar *msg_with_purple_smiley; gchar *msg_utf8_encoded; qq_data *qd; - qq_recv_normal_im_text *im_text; gint bytes = 0; PurpleBuddy *b; - qq_buddy *qq_b; - - g_return_if_fail(common != NULL); - qd = (qq_data *) gc->proto_data; + qq_buddy_data *bd; - /* now it is QQ_NORMAL_IM_TEXT */ - /* - if (*cursor >= (data + len - 1)) { - purple_debug_warning("QQ", "Received normal IM text is empty\n"); - return; - } else - */ - im_text = g_newa(qq_recv_normal_im_text, 1); + struct { + /* now comes the part for text only */ + guint16 msg_seq; + guint32 send_time; + guint16 sender_icon; + guint8 unknown2[3]; + guint8 is_there_font_attr; + guint8 unknown3[4]; + guint8 msg_type; + gchar *msg; /* no fixed length, ends with 0x00 */ + guint8 *font_attr; + gint font_attr_len; + } im_text; - im_text->common = common; + g_return_if_fail (data != NULL && len > 0); + g_return_if_fail(im_header != NULL); + + qd = (qq_data *) gc->proto_data; + memset(&im_text, 0, sizeof(im_text)); /* push data into im_text */ - bytes += qq_get16(&(im_text->msg_seq), data + bytes); - bytes += qq_get32(&(im_text->send_time), data + bytes); - bytes += qq_get16(&(im_text->sender_icon), data + bytes); - bytes += qq_getdata((guint8 *) & (im_text->unknown2), 3, data + bytes); - bytes += qq_get8(&(im_text->is_there_font_attr), data + bytes); + bytes += qq_get16(&(im_text.msg_seq), data + bytes); + bytes += qq_get32(&(im_text.send_time), data + bytes); + bytes += qq_get16(&(im_text.sender_icon), data + bytes); + bytes += qq_getdata((guint8 *) & (im_text.unknown2), 3, data + bytes); + bytes += qq_get8(&(im_text.is_there_font_attr), data + bytes); /** * from lumaqq for unknown3 * totalFragments = buf.get() & 255; * fragmentSequence = buf.get() & 255; * messageId = buf.getChar(); */ - bytes += qq_getdata((guint8 *) & (im_text->unknown3), 4, data + bytes); - bytes += qq_get8(&(im_text->msg_type), data + bytes); + bytes += qq_getdata((guint8 *) & (im_text.unknown3), 4, data + bytes); + bytes += qq_get8(&(im_text.msg_type), data + bytes); /* we need to check if this is auto-reply * QQ2003iii build 0304, returns the msg without font_attr * even the is_there_font_attr shows 0x01, and msg does not ends with 0x00 */ - if (im_text->msg_type == QQ_IM_AUTO_REPLY) { - im_text->is_there_font_attr = 0x00; /* indeed there is no this flag */ - im_text->msg = g_strndup((gchar *)(data + bytes), len - bytes); + if (im_text.msg_type == QQ_IM_AUTO_REPLY) { + im_text.is_there_font_attr = 0x00; /* indeed there is no this flag */ + im_text.msg = g_strndup((gchar *)(data + bytes), len - bytes); } else { /* it is normal mesasge */ - if (im_text->is_there_font_attr) { - im_text->msg = g_strdup((gchar *)(data + bytes)); - bytes += strlen(im_text->msg) + 1; /* length decided by strlen! will it cause a crash? */ - im_text->font_attr_len = len - bytes; - im_text->font_attr = g_memdup(data + bytes, im_text->font_attr_len); - } else /* not im_text->is_there_font_attr */ - im_text->msg = g_strndup((gchar *)(data + bytes), len - bytes); - } /* if im_text->msg_type */ + if (im_text.is_there_font_attr) { + im_text.msg = g_strdup((gchar *)(data + bytes)); + bytes += strlen(im_text.msg) + 1; /* length decided by strlen! will it cause a crash? */ + im_text.font_attr_len = len - bytes; + im_text.font_attr = g_memdup(data + bytes, im_text.font_attr_len); + } else /* not im_text.is_there_font_attr */ + im_text.msg = g_strndup((gchar *)(data + bytes), len - bytes); + } /* if im_text.msg_type */ - name = uid_to_purple_name(common->sender_uid); - b = purple_find_buddy(gc->account, name); + who = uid_to_purple_name(im_header->uid_from); + b = purple_find_buddy(gc->account, who); if (b == NULL) { - qq_add_buddy_by_recv_packet(gc, common->sender_uid, FALSE, TRUE); - b = purple_find_buddy(gc->account, name); + /* create no-auth buddy */ + b = qq_buddy_new(gc, im_header->uid_from); } - qq_b = (b == NULL) ? NULL : (qq_buddy *) b->proto_data; - if (qq_b != NULL) { - qq_b->client_version = common->sender_ver; + bd = (b == NULL) ? NULL : (qq_buddy_data *) b->proto_data; + if (bd != NULL) { + bd->client_tag = im_header->version_from; } - purple_msg_type = (im_text->msg_type == QQ_IM_AUTO_REPLY) ? PURPLE_MESSAGE_AUTO_RESP : 0; + purple_msg_type = (im_text.msg_type == QQ_IM_AUTO_REPLY) ? PURPLE_MESSAGE_AUTO_RESP : 0; - msg_with_purple_smiley = qq_smiley_to_purple(im_text->msg); - msg_utf8_encoded = im_text->is_there_font_attr ? - qq_encode_to_purple(im_text->font_attr, - im_text->font_attr_len, - msg_with_purple_smiley) : qq_to_utf8(msg_with_purple_smiley, QQ_CHARSET_DEFAULT); + msg_with_purple_smiley = qq_smiley_to_purple(im_text.msg); + msg_utf8_encoded = im_text.is_there_font_attr ? + qq_encode_to_purple(im_text.font_attr, + im_text.font_attr_len, + msg_with_purple_smiley, qd->client_version) + : qq_to_utf8(msg_with_purple_smiley, QQ_CHARSET_DEFAULT); - /* send encoded to purple, note that we use im_text->send_time, + /* send encoded to purple, note that we use im_text.send_time, * not the time we receive the message * as it may have been delayed when I am not online. */ - serv_got_im(gc, name, msg_utf8_encoded, purple_msg_type, (time_t) im_text->send_time); + serv_got_im(gc, who, msg_utf8_encoded, purple_msg_type, (time_t) im_text.send_time); g_free(msg_utf8_encoded); g_free(msg_with_purple_smiley); - g_free(name); - g_free(im_text->msg); - if (im_text->is_there_font_attr) - g_free(im_text->font_attr); + g_free(who); + g_free(im_text.msg); + if (im_text.font_attr) g_free(im_text.font_attr); +} + +/* process received extended (2007) text IM */ +static void process_extend_im_text( + PurpleConnection *gc, guint8 *data, gint len, qq_im_header *im_header) +{ + guint16 purple_msg_type; + gchar *who; + gchar *msg_with_purple_smiley; + gchar *msg_utf8_encoded; + qq_data *qd; + PurpleBuddy *b; + qq_buddy_data *bd; + gint bytes, text_len; + + struct { + /* now comes the part for text only */ + guint16 sessionId; + guint32 send_time; + guint16 senderHead; + guint32 flag; + guint8 unknown2[8]; + guint8 fragmentCount; + guint8 fragmentIndex; + guint16 messageId; + guint8 replyType; + gchar *msg; /* no fixed length, ends with 0x00 */ + guint8 fromMobileQQ; + + guint8 is_there_font_attr; + guint8 *font_attr; + gint8 font_attr_len; + } im_text; + + g_return_if_fail (data != NULL && len > 0); + g_return_if_fail(im_header != NULL); + + qd = (qq_data *) gc->proto_data; + memset(&im_text, 0, sizeof(im_text)); + + /* push data into im_text */ + bytes = 0; + bytes += qq_get16(&(im_text.sessionId), data + bytes); + bytes += qq_get32(&(im_text.send_time), data + bytes); + bytes += qq_get16(&(im_text.senderHead), data + bytes); + bytes += qq_get32(&(im_text.flag), data + bytes); + + bytes += qq_getdata(im_text.unknown2, 8, data + bytes); + bytes += qq_get8(&(im_text.fragmentCount), data + bytes); + bytes += qq_get8(&(im_text.fragmentIndex), data + bytes); + + bytes += qq_get16(&(im_text.messageId), data + bytes); + bytes += qq_get8(&(im_text.replyType), data + bytes); + + im_text.font_attr_len = data[len-1] & 0xff; + + text_len = len - bytes - im_text.font_attr_len; + im_text.msg = g_strndup((gchar *)(data + bytes), text_len); + bytes += text_len; + if(im_text.font_attr_len >= 0) + im_text.font_attr = g_memdup(data + bytes, im_text.font_attr_len); + else + { + purple_debug_error("QQ", "Failed to get IM's font attribute len %d\n", + im_text.font_attr_len); + return; + } + + if(im_text.fragmentCount == 0) + im_text.fragmentCount = 1; + + // Filter tail space + if(im_text.fragmentIndex == im_text.fragmentCount -1) + { + gint real_len = text_len; + while(real_len > 0 && im_text.msg[real_len - 1] == 0x20) + real_len --; + + text_len = real_len; + // Null string instaed of space + im_text.msg[text_len] = 0; + } + + who = uid_to_purple_name(im_header->uid_from); + b = purple_find_buddy(gc->account, who); + if (b == NULL) { + /* create no-auth buddy */ + b = qq_buddy_new(gc, im_header->uid_from); + } + bd = (b == NULL) ? NULL : (qq_buddy_data *) b->proto_data; + if (bd != NULL) { + bd->client_tag = im_header->version_from; + } + + purple_msg_type = 0; + + msg_with_purple_smiley = qq_smiley_to_purple(im_text.msg); + msg_utf8_encoded = im_text.font_attr ? + qq_encode_to_purple(im_text.font_attr, + im_text.font_attr_len, + msg_with_purple_smiley, qd->client_version) + : qq_to_utf8(msg_with_purple_smiley, QQ_CHARSET_DEFAULT); + + /* send encoded to purple, note that we use im_text.send_time, + * not the time we receive the message + * as it may have been delayed when I am not online. */ + serv_got_im(gc, who, msg_utf8_encoded, purple_msg_type, (time_t) im_text.send_time); + + g_free(msg_utf8_encoded); + g_free(msg_with_purple_smiley); + g_free(who); + g_free(im_text.msg); + if (im_text.font_attr) g_free(im_text.font_attr); } /* it is a normal IM, maybe text or video request */ -static void _qq_process_recv_normal_im(guint8 *data, gint len, PurpleConnection *gc) +void qq_process_im(PurpleConnection *gc, guint8 *data, gint len) { gint bytes = 0; - qq_recv_normal_im_common *common; - qq_recv_normal_im_unprocessed *im_unprocessed; + qq_im_header im_header; - g_return_if_fail (data != NULL && len != 0); + g_return_if_fail (data != NULL && len > 0); - common = g_newa (qq_recv_normal_im_common, 1); - - bytes = _qq_normal_im_common_read(data, len, common); + bytes = get_im_header(&im_header, data, len); if (bytes < 0) { - purple_debug_error("QQ", "Fail read the common part of normal IM\n"); + purple_debug_error("QQ", "Fail read im header, len %d\n", len); + qq_show_packet ("IM Header", data, len); return; } + purple_debug_info("QQ", + "Got IM to %d, type: %02X from: %d ver: %s (%04X)\n", + im_header.uid_to, im_header.im_type, im_header.uid_from, + qq_get_ver_desc(im_header.version_from), im_header.version_from); - switch (common->normal_im_type) { + switch (im_header.im_type) { case QQ_NORMAL_IM_TEXT: - purple_debug_info("QQ", - "Normal IM, text type:\n [%d] => [%d], src: %s (%04X)\n", - common->sender_uid, common->receiver_uid, - qq_get_ver_desc (common->sender_ver), common->sender_ver); if (bytes >= len - 1) { purple_debug_warning("QQ", "Received normal IM text is empty\n"); return; } - _qq_process_recv_normal_im_text(data + bytes, len - bytes, common, gc); + process_im_text(gc, data + bytes, len - bytes, &im_header); break; case QQ_NORMAL_IM_FILE_REJECT_UDP: - qq_process_recv_file_reject(data + bytes, len - bytes, common->sender_uid, gc); + qq_process_recv_file_reject(data + bytes, len - bytes, im_header.uid_from, gc); break; case QQ_NORMAL_IM_FILE_APPROVE_UDP: - qq_process_recv_file_accept(data + bytes, len - bytes, common->sender_uid, gc); + qq_process_recv_file_accept(data + bytes, len - bytes, im_header.uid_from, gc); break; case QQ_NORMAL_IM_FILE_REQUEST_UDP: - qq_process_recv_file_request(data + bytes, len - bytes, common->sender_uid, gc); + qq_process_recv_file_request(data + bytes, len - bytes, im_header.uid_from, gc); break; case QQ_NORMAL_IM_FILE_CANCEL: - qq_process_recv_file_cancel(data + bytes, len - bytes, common->sender_uid, gc); + qq_process_recv_file_cancel(data + bytes, len - bytes, im_header.uid_from, gc); break; case QQ_NORMAL_IM_FILE_NOTIFY: - qq_process_recv_file_notify(data + bytes, len - bytes, common->sender_uid, gc); + qq_process_recv_file_notify(data + bytes, len - bytes, im_header.uid_from, gc); break; case QQ_NORMAL_IM_FILE_REQUEST_TCP: /* Check ReceivedFileIM::parseContents in eva*/ /* some client use this function for detect invisable buddy*/ - purple_debug_warning("QQ", "Normal IM, not support QQ_NORMAL_IM_FILE_REQUEST_TCP\n"); - qq_show_packet ("Not support", data, len); - break; case QQ_NORMAL_IM_FILE_APPROVE_TCP: - purple_debug_warning("QQ", "Normal IM, not support QQ_NORMAL_IM_FILE_APPROVE_TCP\n"); - qq_show_packet ("Not support", data, len); - break; case QQ_NORMAL_IM_FILE_REJECT_TCP: - purple_debug_warning("QQ", "Normal IM, not support QQ_NORMAL_IM_FILE_REJECT_TCP\n"); - qq_show_packet ("Not support", data, len); - break; case QQ_NORMAL_IM_FILE_PASV: - purple_debug_warning("QQ", "Normal IM, not support QQ_NORMAL_IM_FILE_PASV\n"); - qq_show_packet ("Not support", data, len); - break; case QQ_NORMAL_IM_FILE_EX_REQUEST_UDP: - purple_debug_warning("QQ", "Normal IM, not support QQ_NORMAL_IM_FILE_REQUEST_TCP\n"); - qq_show_packet ("QQ", data, len); - break; case QQ_NORMAL_IM_FILE_EX_REQUEST_ACCEPT: - purple_debug_warning("QQ", "Normal IM, not support QQ_NORMAL_IM_FILE_EX_REQUEST_ACCEPT\n"); - qq_show_packet ("QQ", data, len); - break; case QQ_NORMAL_IM_FILE_EX_REQUEST_CANCEL: - purple_debug_warning("QQ", "Normal IM, not support QQ_NORMAL_IM_FILE_EX_REQUEST_CANCEL\n"); - qq_show_packet ("Not support", data, len); - break; case QQ_NORMAL_IM_FILE_EX_NOTIFY_IP: - purple_debug_warning("QQ", "Normal IM, not support QQ_NORMAL_IM_FILE_EX_NOTIFY_IP\n"); qq_show_packet ("Not support", data, len); break; default: - im_unprocessed = g_newa (qq_recv_normal_im_unprocessed, 1); - im_unprocessed->common = common; - im_unprocessed->unknown = data + bytes; - im_unprocessed->length = len - bytes; /* a simple process here, maybe more later */ - purple_debug_warning("QQ", - "Normal IM, unprocessed type [0x%04x], len %d\n", - common->normal_im_type, im_unprocessed->length); - qq_show_packet ("QQ", im_unprocessed->unknown, im_unprocessed->length); + qq_show_packet ("Unknow", data + bytes, len - bytes); return; } } -/* process im from system administrator */ -static void _qq_process_recv_sys_im(guint8 *data, gint data_len, PurpleConnection *gc) +/* it is a extended IM, maybe text or video request */ +void qq_process_extend_im(PurpleConnection *gc, guint8 *data, gint len) { - gint len; - guint8 reply; - gchar **segments, *msg_utf8; + gint bytes; + qq_im_header im_header; + + g_return_if_fail (data != NULL && len > 0); - g_return_if_fail(data != NULL && data_len != 0); + bytes = get_im_header(&im_header, data, len); + if (bytes < 0) { + purple_debug_error("QQ", "Fail read im header, len %d\n", len); + qq_show_packet ("IM Header", data, len); + return; + } + purple_debug_info("QQ", + "Got Extend IM to %d, type: %02X from: %d ver: %s (%04X)\n", + im_header.uid_to, im_header.im_type, im_header.uid_from, + qq_get_ver_desc(im_header.version_from), im_header.version_from); - len = data_len; - - if (NULL == (segments = split_data(data, len, "\x2f", 2))) - return; - - reply = strtol(segments[0], NULL, 10); - if (reply == QQ_RECV_SYS_IM_KICK_OUT) - purple_debug_warning("QQ", "We are kicked out by QQ server\n"); - msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT); - purple_notify_warning(gc, NULL, _("System Message"), msg_utf8); + switch (im_header.im_type) { + case QQ_NORMAL_IM_TEXT: + process_extend_im_text(gc, data + bytes, len - bytes, &im_header); + break; + case QQ_NORMAL_IM_FILE_REJECT_UDP: + qq_process_recv_file_reject (data + bytes, len - bytes, im_header.uid_from, gc); + break; + case QQ_NORMAL_IM_FILE_APPROVE_UDP: + qq_process_recv_file_accept (data + bytes, len - bytes, im_header.uid_from, gc); + break; + case QQ_NORMAL_IM_FILE_REQUEST_UDP: + qq_process_recv_file_request (data + bytes, len - bytes, im_header.uid_from, gc); + break; + case QQ_NORMAL_IM_FILE_CANCEL: + qq_process_recv_file_cancel (data + bytes, len - bytes, im_header.uid_from, gc); + break; + case QQ_NORMAL_IM_FILE_NOTIFY: + qq_process_recv_file_notify (data + bytes, len - bytes, im_header.uid_from, gc); + break; + default: + /* a simple process here, maybe more later */ + qq_show_packet ("Unknow", data + bytes, len - bytes); + break; + } } -/* send an IM to to_uid */ -void qq_send_packet_im(PurpleConnection *gc, guint32 to_uid, gchar *msg, gint type) +/* send an IM to uid_to */ +void qq_request_send_im(PurpleConnection *gc, guint32 uid_to, gchar *msg, gint type) { qq_data *qd; guint8 *raw_data, *send_im_tail; - guint16 client_tag, normal_im_type; + guint16 im_type; gint msg_len, raw_len, font_name_len, tail_len, bytes; time_t now; gchar *msg_filtered; @@ -512,8 +506,7 @@ const gchar *start, *end, *last; qd = (qq_data *) gc->proto_data; - client_tag = QQ_CLIENT; - normal_im_type = QQ_NORMAL_IM_TEXT; + im_type = QQ_NORMAL_IM_TEXT; last = msg; while (purple_markup_find_tag("font", last, &start, &end, &attribs)) { @@ -570,17 +563,17 @@ /* 000-003: receiver uid */ bytes += qq_put32(raw_data + bytes, qd->uid); /* 004-007: sender uid */ - bytes += qq_put32(raw_data + bytes, to_uid); + bytes += qq_put32(raw_data + bytes, uid_to); /* 008-009: sender client version */ - bytes += qq_put16(raw_data + bytes, client_tag); + bytes += qq_put16(raw_data + bytes, qd->client_tag); /* 010-013: receiver uid */ bytes += qq_put32(raw_data + bytes, qd->uid); /* 014-017: sender uid */ - bytes += qq_put32(raw_data + bytes, to_uid); + bytes += qq_put32(raw_data + bytes, uid_to); /* 018-033: md5 of (uid+session_key) */ bytes += qq_putdata(raw_data + bytes, qd->session_md5, 16); /* 034-035: message type */ - bytes += qq_put16(raw_data + bytes, normal_im_type); + bytes += qq_put16(raw_data + bytes, im_type); /* 036-037: sequence number */ bytes += qq_put16(raw_data + bytes, qd->send_seq); /* 038-041: send time */ @@ -600,10 +593,10 @@ bytes += qq_putdata(raw_data + bytes, (guint8 *) msg_filtered, msg_len); send_im_tail = qq_get_send_im_tail(font_color, font_size, font_name, is_bold, is_italic, is_underline, tail_len); - qq_show_packet("QQ_send_im_tail debug", send_im_tail, tail_len); + /* qq_show_packet("qq_get_send_im_tail", send_im_tail, tail_len); */ bytes += qq_putdata(raw_data + bytes, send_im_tail, tail_len); - qq_show_packet("QQ_raw_data debug", raw_data, bytes); + /* qq_show_packet("QQ_CMD_SEND_IM, raw_data, bytes); */ if (bytes == raw_len) /* create packet OK */ qq_send_cmd(gc, QQ_CMD_SEND_IM, raw_data, bytes); @@ -619,127 +612,5 @@ g_free(msg_filtered); } -/* parse the reply to send_im */ -void qq_process_send_im_reply(guint8 *data, gint data_len, PurpleConnection *gc) -{ - qq_data *qd; - - g_return_if_fail(data != NULL && data_len != 0); - - qd = gc->proto_data; - - if (data[0] != QQ_SEND_IM_REPLY_OK) { - purple_debug_warning("QQ", "Send IM fail\n"); - purple_notify_error(gc, _("Error"), _("Failed to send IM."), NULL); - } else { - purple_debug_info("QQ", "IM ACK OK\n"); - } -} - -/* I receive a message, mainly it is text msg, - * but we need to proess other types (group etc) */ -void qq_process_recv_im(guint8 *data, gint data_len, guint16 seq, PurpleConnection *gc) -{ - qq_data *qd; - gint bytes; - qq_recv_im_header *im_header; - - g_return_if_fail(data != NULL && data_len != 0); - - qd = (qq_data *) gc->proto_data; - - if (data_len < 16) { /* we need to ack with the first 16 bytes */ - purple_debug_error("QQ", "MSG is too short\n"); - return; - } else { - /* when we receive a message, - * we send an ACK which is the first 16 bytes of incoming packet */ - qq_send_server_reply(gc, QQ_CMD_RECV_IM, seq, data, 16); - } - - /* check len first */ - if (data_len < 20) { /* length of im_header */ - purple_debug_error("QQ", "Invald MSG header, len %d < 20\n", data_len); - return; - } - - bytes = 0; - im_header = g_newa(qq_recv_im_header, 1); - bytes += qq_get32(&(im_header->sender_uid), data + bytes); - bytes += qq_get32(&(im_header->receiver_uid), data + bytes); - bytes += qq_get32(&(im_header->server_im_seq), data + bytes); - /* if the message is delivered via server, it is server IP/port */ - bytes += qq_getIP(&(im_header->sender_ip), data + bytes); - bytes += qq_get16(&(im_header->sender_port), data + bytes); - bytes += qq_get16(&(im_header->im_type), data + bytes); - /* im_header prepared */ - - if (im_header->receiver_uid != qd->uid) { /* should not happen */ - purple_debug_error("QQ", "MSG to [%d], NOT me\n", im_header->receiver_uid); - return; - } - /* check bytes */ - if (bytes >= data_len - 1) { - purple_debug_warning("QQ", "Empty MSG\n"); - return; - } - switch (im_header->im_type) { - case QQ_RECV_IM_NEWS: - _qq_process_recv_news(data + bytes, data_len - bytes, gc); - break; - case QQ_RECV_IM_FROM_BUDDY_2006: - case QQ_RECV_IM_FROM_UNKNOWN_2006: - case QQ_RECV_IM_TO_UNKNOWN: - case QQ_RECV_IM_TO_BUDDY: - purple_debug_info("QQ", "MSG from buddy [%d]\n", im_header->sender_uid); - _qq_process_recv_normal_im(data + bytes, data_len - bytes, gc); - break; - case QQ_RECV_IM_UNKNOWN_QUN_IM: - case QQ_RECV_IM_TEMP_QUN_IM: - case QQ_RECV_IM_QUN_IM: - purple_debug_info("QQ", "MSG from room [%d]\n", im_header->sender_uid); - /* sender_uid is in fact id */ - qq_process_room_msg_normal(data + bytes, data_len - bytes, im_header->sender_uid, gc, im_header->im_type); - break; - case QQ_RECV_IM_ADD_TO_QUN: - purple_debug_info("QQ", "Notice from [%d], Added\n", im_header->sender_uid); - /* sender_uid is group id - * we need this to create a dummy group and add to blist */ - qq_process_room_msg_been_added(data + bytes, data_len - bytes, im_header->sender_uid, gc); - break; - case QQ_RECV_IM_DEL_FROM_QUN: - purple_debug_info("QQ", "Notice from room [%d], Removed\n", im_header->sender_uid); - /* sender_uid is group id */ - qq_process_room_msg_been_removed(data + bytes, data_len - bytes, im_header->sender_uid, gc); - break; - case QQ_RECV_IM_APPLY_ADD_TO_QUN: - purple_debug_info("QQ", "Notice from room [%d], Joined\n", im_header->sender_uid); - /* sender_uid is group id */ - qq_process_room_msg_apply_join(data + bytes, data_len - bytes, im_header->sender_uid, gc); - break; - case QQ_RECV_IM_APPROVE_APPLY_ADD_TO_QUN: - purple_debug_info("QQ", "Notice from room [%d], Confirm add in\n", - im_header->sender_uid); - /* sender_uid is group id */ - qq_process_room_msg_been_approved(data + bytes, data_len - bytes, im_header->sender_uid, gc); - break; - case QQ_RECV_IM_REJCT_APPLY_ADD_TO_QUN: - purple_debug_info("QQ", "Notice from room [%d], Refuse add in\n", - im_header->sender_uid); - /* sender_uid is group id */ - qq_process_room_msg_been_rejected(data + bytes, data_len - bytes, im_header->sender_uid, gc); - break; - case QQ_RECV_IM_SYS_NOTIFICATION: - purple_debug_info("QQ", "Admin notice from [%d]\n", im_header->sender_uid); - _qq_process_recv_sys_im(data + bytes, data_len - bytes, gc); - break; - default: - purple_debug_warning("QQ", "MSG from [%d], unknown type %s [0x%02x]\n", - im_header->sender_uid, qq_get_recv_im_type_str(im_header->im_type), - im_header->im_type); - qq_show_packet("Unknown MSG type", data, data_len); - } -} - diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/im.h --- a/libpurple/protocols/qq/im.h Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/im.h Wed Oct 29 21:38:53 2008 +0000 @@ -39,30 +39,33 @@ }; enum { - QQ_RECV_IM_TO_BUDDY = 0x0009, - QQ_RECV_IM_TO_UNKNOWN = 0x000a, - QQ_RECV_IM_NEWS = 0x0018, - QQ_RECV_IM_UNKNOWN_QUN_IM = 0x0020, - QQ_RECV_IM_ADD_TO_QUN = 0x0021, - QQ_RECV_IM_DEL_FROM_QUN = 0x0022, - QQ_RECV_IM_APPLY_ADD_TO_QUN = 0x0023, - QQ_RECV_IM_APPROVE_APPLY_ADD_TO_QUN = 0x0024, - QQ_RECV_IM_REJCT_APPLY_ADD_TO_QUN = 0x0025, - QQ_RECV_IM_CREATE_QUN = 0x0026, - QQ_RECV_IM_TEMP_QUN_IM = 0x002A, - QQ_RECV_IM_QUN_IM = 0x002B, - QQ_RECV_IM_SYS_NOTIFICATION = 0x0030, - QQ_RECV_IM_FROM_BUDDY_2006 = 0x0084, - QQ_RECV_IM_FROM_UNKNOWN_2006 = 0x0085, + QQ_MSG_TO_BUDDY = 0x0009, + QQ_MSG_TO_UNKNOWN = 0x000a, + QQ_MSG_NEWS = 0x0018, + QQ_MSG_UNKNOWN_QUN_IM = 0x0020, + QQ_MSG_ADD_TO_QUN = 0x0021, + QQ_MSG_DEL_FROM_QUN = 0x0022, + QQ_MSG_APPLY_ADD_TO_QUN = 0x0023, + QQ_MSG_APPROVE_APPLY_ADD_TO_QUN = 0x0024, + QQ_MSG_REJCT_APPLY_ADD_TO_QUN = 0x0025, + QQ_MSG_CREATE_QUN = 0x0026, + QQ_MSG_TEMP_QUN_IM = 0x002A, + QQ_MSG_QUN_IM = 0x002B, + QQ_MSG_SYS_30 = 0x0030, + QQ_MSG_SYS_4C = 0x004C, + QQ_MSG_EXTEND = 0x0084, + QQ_MSG_EXTEND_85 = 0x0085, }; -guint8 *qq_get_send_im_tail(const gchar *font_color, - const gchar *font_size, - const gchar *font_name, - gboolean is_bold, gboolean is_italic, gboolean is_underline, gint len); +void qq_got_attention(PurpleConnection *gc, const gchar *msg); -void qq_send_packet_im(PurpleConnection *gc, guint32 to_uid, gchar *msg, gint type); -void qq_process_recv_im(guint8 *data, gint data_len, guint16 seq, PurpleConnection *gc); -void qq_process_send_im_reply(guint8 *data, gint data_len, PurpleConnection *gc); +guint8 *qq_get_send_im_tail(const gchar *font_color, + const gchar *font_size, + const gchar *font_name, + gboolean is_bold, gboolean is_italic, gboolean is_underline, gint len); +void qq_request_send_im(PurpleConnection *gc, guint32 uid_to, gchar *msg, gint type); + +void qq_process_im(PurpleConnection *gc, guint8 *data, gint len); +void qq_process_extend_im(PurpleConnection *gc, guint8 *data, gint len); #endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/packet_parse.c --- a/libpurple/protocols/qq/packet_parse.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/packet_parse.c Wed Oct 29 21:38:53 2008 +0000 @@ -38,7 +38,7 @@ #define PARSER_DEBUG #endif -/* read one byte from buf, +/* read one byte from buf, * return the number of bytes read if succeeds, otherwise return -1 */ gint qq_get8(guint8 *b, guint8 *buf) { @@ -53,7 +53,7 @@ } -/* read two bytes as "guint16" from buf, +/* read two bytes as "guint16" from buf, * return the number of bytes read if succeeds, otherwise return -1 */ gint qq_get16(guint16 *w, guint8 *buf) { @@ -67,7 +67,7 @@ return sizeof(w_dest); } -/* read four bytes as "guint32" from buf, +/* read four bytes as "guint32" from buf, * return the number of bytes read if succeeds, otherwise return -1 */ gint qq_get32(guint32 *dw, guint8 *buf) { @@ -87,7 +87,7 @@ return sizeof(struct in_addr); } -/* read datalen bytes from buf, +/* read datalen bytes from buf, * return the number of bytes read if succeeds, otherwise return -1 */ gint qq_getdata(guint8 *data, gint datalen, guint8 *buf) { @@ -151,7 +151,20 @@ * return the number of bytes packed, otherwise return -1 */ gint qq_put32(guint8 *buf, guint32 dw) { - guint32 dw_porter; + guint32 dw_porter; + dw_porter = g_htonl(dw); +#ifdef PARSER_DEBUG + purple_debug_info("QQ", "[DBG][put32] buf %p\n", (void *)buf); + purple_debug_info("QQ", "[DBG][put32] dw 0x%08x, dw_porter 0x%08x\n", dw, dw_porter); +#endif + memcpy(buf, &dw_porter, sizeof(dw_porter)); + return sizeof(dw_porter); +} + +gint qq_putime(guint8 *buf, time_t *t) +{ + guint32 dw, dw_porter; + memcpy(&dw, t, sizeof(dw)); dw_porter = g_htonl(dw); #ifdef PARSER_DEBUG purple_debug_info("QQ", "[DBG][put32] buf %p\n", (void *)buf); @@ -171,7 +184,7 @@ * return the number of bytes packed, otherwise return -1 */ gint qq_putdata(guint8 *buf, const guint8 *data, const int datalen) { - memcpy(buf, data, datalen); + memcpy(buf, data, datalen); #ifdef PARSER_DEBUG purple_debug_info("QQ", "[DBG][putdata] buf %p\n", (void *)buf); #endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/packet_parse.h --- a/libpurple/protocols/qq/packet_parse.h Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/packet_parse.h Wed Oct 29 21:38:53 2008 +0000 @@ -54,6 +54,7 @@ gint qq_put16(guint8 *buf, guint16 w); gint qq_put32(guint8 *buf, guint32 dw); gint qq_putIP(guint8* buf, struct in_addr *ip); +gint qq_putime(guint8 *buf, time_t *t); gint qq_putdata(guint8 *buf, const guint8 *data, const int datalen); /* diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/qq.c --- a/libpurple/protocols/qq/qq.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/qq.c Wed Oct 29 21:38:53 2008 +0000 @@ -29,6 +29,7 @@ #include "notify.h" #include "prefs.h" #include "prpl.h" +#include "privacy.h" #include "request.h" #include "roomlist.h" #include "server.h" @@ -39,12 +40,12 @@ #include "buddy_list.h" #include "char_conv.h" #include "group.h" -#include "group_find.h" #include "group_im.h" #include "group_info.h" #include "group_join.h" #include "group_opt.h" -#include "header_info.h" +#include "group_internal.h" +#include "qq_define.h" #include "im.h" #include "qq_process.h" #include "qq_base.h" @@ -56,7 +57,11 @@ #include "version.h" #define OPENQ_AUTHOR "Puzzlebird" -#define OPENQ_WEBSITE "http://openq.sourceforge.net" +#define OPENQ_WEBSITE "http://openq.sourceforge.net" + +#ifndef OPENQ_VERSION +#define OPENQ_VERSION DISPLAY_VERSION +#endif static GList *server_list_build(gchar select) { @@ -128,6 +133,7 @@ PurpleConnection *gc; qq_data *qd; PurplePresence *presence; + const gchar *version_str; g_return_if_fail(account != NULL); @@ -154,6 +160,19 @@ server_list_create(account); purple_debug_info("QQ", "Server list has %d\n", g_list_length(qd->servers)); + version_str = purple_account_get_string(account, "client_version", NULL); + qd->client_tag = QQ_CLIENT_0D55; /* set default as QQ2005 */ + qd->client_version = 2005; + if (version_str != NULL && strlen(version_str) != 0) { + if (strcmp(version_str, "qq2007") == 0) { + qd->client_tag = QQ_CLIENT_111D; + qd->client_version = 2007; + } else if (strcmp(version_str, "qq2008") == 0) { + qd->client_tag = QQ_CLIENT_115B; + qd->client_version = 2008; + } + } + qd->is_show_notice = purple_account_get_bool(account, "show_notice", TRUE); qd->is_show_news = purple_account_get_bool(account, "show_news", TRUE); @@ -203,6 +222,13 @@ } qq_disconnect(gc); + + if (qd->redirect) g_free(qd->redirect); + if (qd->ld.token) g_free(qd->ld.token); + if (qd->ld.token_ex) g_free(qd->ld.token_ex); + if (qd->captcha.token) g_free(qd->captcha.token); + if (qd->captcha.data) g_free(qd->captcha.data); + server_list_remove_all(qd); g_free(qd); @@ -210,25 +236,25 @@ } /* returns the icon name for a buddy or protocol */ -static const gchar *_qq_list_icon(PurpleAccount *a, PurpleBuddy *b) +static const gchar *qq_list_icon(PurpleAccount *a, PurpleBuddy *b) { return "qq"; } /* a short status text beside buddy icon*/ -static gchar *_qq_status_text(PurpleBuddy *b) +static gchar *qq_status_text(PurpleBuddy *b) { - qq_buddy *q_bud; + qq_buddy_data *bd; GString *status; - q_bud = (qq_buddy *) b->proto_data; - if (q_bud == NULL) + bd = (qq_buddy_data *) b->proto_data; + if (bd == NULL) return NULL; status = g_string_new(""); - switch(q_bud->status) { + switch(bd->status) { case QQ_BUDDY_OFFLINE: g_string_append(status, _("Offline")); break; @@ -245,8 +271,11 @@ case QQ_BUDDY_ONLINE_INVISIBLE: g_string_append(status, _("Invisible")); break; + case QQ_BUDDY_ONLINE_BUSY: + g_string_append(status, _("Busy")); + break; default: - g_string_printf(status, _("Unknown-%d"), q_bud->status); + g_string_printf(status, _("Unknown-%d"), bd->status); } return g_string_free(status, FALSE); @@ -254,23 +283,23 @@ /* a floating text when mouse is on the icon, show connection status here */ -static void _qq_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full) +static void qq_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full) { - qq_buddy *q_bud; + qq_buddy_data *bd; gchar *tmp; GString *str; g_return_if_fail(b != NULL); - q_bud = (qq_buddy *) b->proto_data; - if (q_bud == NULL) + bd = (qq_buddy_data *) b->proto_data; + if (bd == NULL) return; - /* if (PURPLE_BUDDY_IS_ONLINE(b) && q_bud != NULL) */ - if (q_bud->ip.s_addr != 0) { + /* if (PURPLE_BUDDY_IS_ONLINE(b) && bd != NULL) */ + if (bd->ip.s_addr != 0) { str = g_string_new(NULL); - g_string_printf(str, "%s:%d", inet_ntoa(q_bud->ip), q_bud->port); - if (q_bud->comm_flag & QQ_COMM_FLAG_TCP_MODE) { + g_string_printf(str, "%s:%d", inet_ntoa(bd->ip), bd->port); + if (bd->comm_flag & QQ_COMM_FLAG_TCP_MODE) { g_string_append(str, " TCP"); } else { g_string_append(str, " UDP"); @@ -278,11 +307,11 @@ g_string_free(str, TRUE); } - tmp = g_strdup_printf("%d", q_bud->age); + tmp = g_strdup_printf("%d", bd->age); purple_notify_user_info_add_pair(user_info, _("Age"), tmp); g_free(tmp); - switch (q_bud->gender) { + switch (bd->gender) { case QQ_BUDDY_GENDER_GG: purple_notify_user_info_add_pair(user_info, _("Gender"), _("Male")); break; @@ -293,38 +322,38 @@ purple_notify_user_info_add_pair(user_info, _("Gender"), _("Unknown")); break; default: - tmp = g_strdup_printf("Error (%d)", q_bud->gender); + tmp = g_strdup_printf("Error (%d)", bd->gender); purple_notify_user_info_add_pair(user_info, _("Gender"), tmp); g_free(tmp); } - if (q_bud->level) { - tmp = g_strdup_printf("%d", q_bud->level); + if (bd->level) { + tmp = g_strdup_printf("%d", bd->level); purple_notify_user_info_add_pair(user_info, _("Level"), tmp); g_free(tmp); } str = g_string_new(NULL); - if (q_bud->comm_flag & QQ_COMM_FLAG_QQ_MEMBER) { + if (bd->comm_flag & QQ_COMM_FLAG_QQ_MEMBER) { g_string_append( str, _("Member") ); } - if (q_bud->comm_flag & QQ_COMM_FLAG_QQ_VIP) { + if (bd->comm_flag & QQ_COMM_FLAG_QQ_VIP) { g_string_append( str, _(" VIP") ); } - if (q_bud->comm_flag & QQ_COMM_FLAG_TCP_MODE) { + if (bd->comm_flag & QQ_COMM_FLAG_TCP_MODE) { g_string_append( str, _(" TCP") ); } - if (q_bud->comm_flag & QQ_COMM_FLAG_MOBILE) { + if (bd->comm_flag & QQ_COMM_FLAG_MOBILE) { g_string_append( str, _(" FromMobile") ); } - if (q_bud->comm_flag & QQ_COMM_FLAG_BIND_MOBILE) { + if (bd->comm_flag & QQ_COMM_FLAG_BIND_MOBILE) { g_string_append( str, _(" BindMobile") ); } - if (q_bud->comm_flag & QQ_COMM_FLAG_VIDEO) { + if (bd->comm_flag & QQ_COMM_FLAG_VIDEO) { g_string_append( str, _(" Video") ); } - if (q_bud->ext_flag & QQ_EXT_FLAG_ZONE) { + if (bd->ext_flag & QQ_EXT_FLAG_ZONE) { g_string_append( str, _(" Zone") ); } purple_notify_user_info_add_pair(user_info, _("Flag"), str->str); @@ -333,40 +362,47 @@ #ifdef DEBUG tmp = g_strdup_printf( "%s (%04X)", - qq_get_ver_desc(q_bud->client_version), - q_bud->client_version ); + qq_get_ver_desc(bd->client_tag), + bd->client_tag ); purple_notify_user_info_add_pair(user_info, _("Ver"), tmp); g_free(tmp); tmp = g_strdup_printf( "Ext 0x%X, Comm 0x%X", - q_bud->ext_flag, q_bud->comm_flag ); + bd->ext_flag, bd->comm_flag ); purple_notify_user_info_add_pair(user_info, _("Flag"), tmp); g_free(tmp); #endif } /* we can show tiny icons on the four corners of buddy icon, */ -static const char *_qq_list_emblem(PurpleBuddy *b) +static const char *qq_list_emblem(PurpleBuddy *b) { - /* each char** are refering to a filename in pixmaps/purple/status/default/ */ - qq_buddy *q_bud; + PurpleAccount *account; + PurpleConnection *gc; + qq_data *qd; + qq_buddy_data *buddy; - if (!b || !(q_bud = b->proto_data)) { + if (!b || !(account = b->account) || + !(gc = purple_account_get_connection(account)) || !(qd = gc->proto_data)) return NULL; + + buddy = (qq_buddy_data *)b->proto_data; + if (!buddy) { + return "not-authorized"; } - if (q_bud->comm_flag & QQ_COMM_FLAG_MOBILE) + if (buddy->comm_flag & QQ_COMM_FLAG_MOBILE) return "mobile"; - if (q_bud->comm_flag & QQ_COMM_FLAG_VIDEO) + if (buddy->comm_flag & QQ_COMM_FLAG_VIDEO) return "video"; - if (q_bud->comm_flag & QQ_COMM_FLAG_QQ_MEMBER) + if (buddy->comm_flag & QQ_COMM_FLAG_QQ_MEMBER) return "qq_member"; return NULL; } /* QQ away status (used to initiate QQ away packet) */ -static GList *_qq_away_states(PurpleAccount *ga) +static GList *qq_status_types(PurpleAccount *ga) { PurpleStatusType *status; GList *types = NULL; @@ -383,6 +419,10 @@ "invisible", _("Invisible"), FALSE, TRUE, FALSE); types = g_list_append(types, status); + status = purple_status_type_new_full(PURPLE_STATUS_UNAVAILABLE, + "busy", _("Busy"), TRUE, TRUE, FALSE); + types = g_list_append(types, status); + status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, "offline", _("Offline"), FALSE, TRUE, FALSE); types = g_list_append(types, status); @@ -395,18 +435,72 @@ } /* initiate QQ away with proper change_status packet */ -static void _qq_change_status(PurpleAccount *account, PurpleStatus *status) +static void qq_change_status(PurpleAccount *account, PurpleStatus *status) { PurpleConnection *gc = purple_account_get_connection(account); qq_request_change_status(gc, 0); } +static void qq_add_deny(PurpleConnection *gc, const char *who) +{ + qq_data *qd; + g_return_if_fail(NULL != gc && NULL != gc->proto_data); + + qd = (qq_data *) gc->proto_data; + if (!qd->is_login) + return; + + if (!who || who[0] == '\0') + return; + + purple_debug_info("QQ", "Add deny for %s\n", who); +} + +static void qq_rem_deny(PurpleConnection *gc, const char *who) +{ + qq_data *qd; + g_return_if_fail(NULL != gc && NULL != gc->proto_data); + + qd = (qq_data *) gc->proto_data; + if (!qd->is_login) + return; + + if (!who || who[0] == '\0') + return; + + purple_debug_info("QQ", "Rem deny for %s\n", who); +} + +static void qq_set_permit_deny(PurpleConnection *gc) +{ + PurpleAccount *account; + GSList *deny; + + purple_debug_info("QQ", "Set permit deny\n"); + account = purple_connection_get_account(gc); + switch (account->perm_deny) + { + case PURPLE_PRIVACY_ALLOW_ALL: + for (deny = account->deny; deny; deny = deny->next) + qq_rem_deny(gc, deny->data); + break; + + case PURPLE_PRIVACY_ALLOW_BUDDYLIST: + case PURPLE_PRIVACY_ALLOW_USERS: + case PURPLE_PRIVACY_DENY_USERS: + case PURPLE_PRIVACY_DENY_ALL: + for (deny = account->deny; deny; deny = deny->next) + qq_add_deny(gc, deny->data); + break; + } +} + /* IMPORTANT: PurpleConvImFlags -> PurpleMessageFlags */ /* send an instant msg to a buddy */ -static gint _qq_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, PurpleMessageFlags flags) +static gint qq_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, PurpleMessageFlags flags) { - gint type, to_uid; + gint type, uid_to; gchar *msg, *msg_with_qq_smiley; qq_data *qd; @@ -417,15 +511,15 @@ g_return_val_if_fail(strlen(message) <= QQ_MSG_IM_MAX, -E2BIG); type = (flags == PURPLE_MESSAGE_AUTO_RESP ? QQ_IM_AUTO_REPLY : QQ_IM_TEXT); - to_uid = purple_name_to_uid(who); + uid_to = purple_name_to_uid(who); /* if msg is to myself, bypass the network */ - if (to_uid == qd->uid) { + if (uid_to == qd->uid) { serv_got_im(gc, who, message, flags, time(NULL)); } else { msg = utf8_to_qq(message, QQ_CHARSET_DEFAULT); msg_with_qq_smiley = purple_smiley_to_qq(msg); - qq_send_packet_im(gc, to_uid, msg_with_qq_smiley, type); + qq_request_send_im(gc, uid_to, msg_with_qq_smiley, type); g_free(msg); g_free(msg_with_qq_smiley); } @@ -434,21 +528,18 @@ } /* send a chat msg to a QQ Qun */ -static int _qq_chat_send(PurpleConnection *gc, int channel, const char *message, PurpleMessageFlags flags) +static int qq_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags) { gchar *msg, *msg_with_qq_smiley; - qq_group *group; + guint32 room_id = id; g_return_val_if_fail(message != NULL, -1); g_return_val_if_fail(strlen(message) <= QQ_MSG_IM_MAX, -E2BIG); - group = qq_group_find_by_channel(gc, channel); - g_return_val_if_fail(group != NULL, -1); - purple_debug_info("QQ_MESG", "Send qun mesg in utf8: %s\n", message); msg = utf8_to_qq(message, QQ_CHARSET_DEFAULT); msg_with_qq_smiley = purple_smiley_to_qq(msg); - qq_send_packet_group_im(gc, group, msg_with_qq_smiley); + qq_request_room_send_im(gc, room_id, msg_with_qq_smiley); g_free(msg); g_free(msg_with_qq_smiley); @@ -456,7 +547,7 @@ } /* send packet to get who's detailed information */ -static void _qq_get_info(PurpleConnection *gc, const gchar *who) +static void qq_show_buddy_info(PurpleConnection *gc, const gchar *who) { guint32 uid; qq_data *qd; @@ -470,78 +561,135 @@ return; } - qq_request_get_level(gc, uid); - qq_send_packet_get_info(gc, uid, TRUE); + if (qd->client_version >= 2007) { + qq_request_get_level_2007(gc, uid); + } else { + qq_request_get_level(gc, uid); + } + qq_request_buddy_info(gc, uid, 0, QQ_BUDDY_INFO_DISPLAY); +} + +static void action_update_all_rooms(PurplePluginAction *action) +{ + PurpleConnection *gc = (PurpleConnection *) action->context; + qq_data *qd; + + g_return_if_fail(NULL != gc && NULL != gc->proto_data); + qd = (qq_data *) gc->proto_data; + + if ( !qd->is_login ) { + return; + } + + qq_update_all_rooms(gc, 0, 0); } -/* get my own information */ -static void _qq_menu_modify_my_info(PurplePluginAction *action) +static void action_change_icon(PurplePluginAction *action) +{ + PurpleConnection *gc = (PurpleConnection *) action->context; + qq_data *qd; + gchar *icon_name; + gchar *icon_path; + + g_return_if_fail(NULL != gc && NULL != gc->proto_data); + qd = (qq_data *) gc->proto_data; + + if ( !qd->is_login ) { + return; + } + + icon_name = qq_get_icon_name(qd->my_icon); + icon_path = qq_get_icon_path(icon_name); + g_free(icon_name); + + purple_debug_info("QQ", "Change prev icon %s to ...\n", icon_path); + purple_request_file(action, _("Select icon..."), icon_path, + FALSE, + G_CALLBACK(qq_change_icon_cb), NULL, + purple_connection_get_account(gc), NULL, NULL, + gc); + g_free(icon_path); +} + +static void action_modify_info_base(PurplePluginAction *action) +{ + PurpleConnection *gc = (PurpleConnection *) action->context; + qq_data *qd; + + g_return_if_fail(NULL != gc && NULL != gc->proto_data); + qd = (qq_data *) gc->proto_data; + qq_request_buddy_info(gc, qd->uid, 0, QQ_BUDDY_INFO_MODIFY_BASE); +} + +static void action_modify_info_ext(PurplePluginAction *action) { PurpleConnection *gc = (PurpleConnection *) action->context; qq_data *qd; + g_return_if_fail(NULL != gc && NULL != gc->proto_data); qd = (qq_data *) gc->proto_data; - qq_prepare_modify_info(gc); + qq_request_buddy_info(gc, qd->uid, 0, QQ_BUDDY_INFO_MODIFY_EXT); +} + +static void action_modify_info_addr(PurplePluginAction *action) +{ + PurpleConnection *gc = (PurpleConnection *) action->context; + qq_data *qd; + + g_return_if_fail(NULL != gc && NULL != gc->proto_data); + qd = (qq_data *) gc->proto_data; + qq_request_buddy_info(gc, qd->uid, 0, QQ_BUDDY_INFO_MODIFY_ADDR); } -static void _qq_menu_change_password(PurplePluginAction *action) +static void action_modify_info_contact(PurplePluginAction *action) { + PurpleConnection *gc = (PurpleConnection *) action->context; + qq_data *qd; + + g_return_if_fail(NULL != gc && NULL != gc->proto_data); + qd = (qq_data *) gc->proto_data; + qq_request_buddy_info(gc, qd->uid, 0, QQ_BUDDY_INFO_MODIFY_CONTACT); +} + +static void action_change_password(PurplePluginAction *action) +{ + PurpleConnection *gc = (PurpleConnection *) action->context; + + g_return_if_fail(NULL != gc && NULL != gc->proto_data); purple_notify_uri(NULL, "https://password.qq.com"); } -/* remove a buddy from my list and remove myself from his list */ -/* TODO: re-enable this -static void _qq_menu_block_buddy(PurpleBlistNode * node) -{ - guint32 uid; - gc_and_uid *g; - PurpleBuddy *buddy; - PurpleConnection *gc; - const gchar *who; - - g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); - - buddy = (PurpleBuddy *) node; - gc = purple_account_get_connection(buddy->account); - who = buddy->name; - g_return_if_fail(who != NULL); - - uid = purple_name_to_uid(who); - g_return_if_fail(uid > 0); - - g = g_new0(gc_and_uid, 1); - g->gc = gc; - g->uid = uid; - - purple_request_action(gc, _("Block Buddy"), - _("Are you sure you want to block this buddy?"), NULL, - 1, g, 2, - _("Cancel"), - G_CALLBACK(qq_do_nothing_with_gc_and_uid), - _("Block"), G_CALLBACK(qq_block_buddy_with_gc_and_uid)); -} -*/ - /* show a brief summary of what we get from login packet */ -static void _qq_menu_account_info(PurplePluginAction *action) +static void action_show_account_info(PurplePluginAction *action) { PurpleConnection *gc = (PurpleConnection *) action->context; qq_data *qd; GString *info; + struct tm *tm_local; + int index; + g_return_if_fail(NULL != gc && NULL != gc->proto_data); qd = (qq_data *) gc->proto_data; - info = g_string_new("\n"); + info = g_string_new(""); - g_string_append_printf(info, _("Current Online: %d
\n"), qd->total_online); - g_string_append_printf(info, _("Last Refresh: %s
\n"), ctime(&qd->last_get_online)); + tm_local = localtime(&qd->login_time); + g_string_append_printf(info, _("Login time: %d-%d-%d, %d:%d:%d
\n"), + (1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday, + tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec); + g_string_append_printf(info, _("Total Online Buddies: %d
\n"), qd->online_total); + tm_local = localtime(&qd->online_last_update); + g_string_append_printf(info, _("Last Refresh: %d-%d-%d, %d:%d:%d
\n"), + (1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday, + tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec); - g_string_append(info, "
\n"); + g_string_append(info, "
"); g_string_append_printf(info, _("Server: %s
\n"), qd->curr_server); + g_string_append_printf(info, _("Client Tag: %s
\n"), qq_get_ver_desc(qd->client_tag)); g_string_append_printf(info, _("Connection Mode: %s
\n"), qd->use_tcp ? "TCP" : "UDP"); - g_string_append_printf(info, _("My Internet Address: %s
\n"), inet_ntoa(qd->my_ip)); + g_string_append_printf(info, _("My Internet IP: %s:%d
\n"), inet_ntoa(qd->my_ip), qd->my_port); - g_string_append(info, "
\n"); + g_string_append(info, "
"); g_string_append(info, "Network Status
\n"); g_string_append_printf(info, _("Sent: %lu
\n"), qd->net_stat.sent); g_string_append_printf(info, _("Resend: %lu
\n"), qd->net_stat.resend); @@ -549,12 +697,18 @@ g_string_append_printf(info, _("Received: %lu
\n"), qd->net_stat.rcved); g_string_append_printf(info, _("Received Duplicate: %lu
\n"), qd->net_stat.rcved_dup); - g_string_append(info, "
\n"); - g_string_append(info, "Information below may not be accurate
\n"); + g_string_append(info, "
"); + g_string_append(info, "Last Login Information
\n"); - g_string_append_printf(info, _("Login Time: %s
\n"), ctime(&qd->login_time)); - g_string_append_printf(info, _("Last Login IP: %s
\n"), qd->last_login_ip); - g_string_append_printf(info, _("Last Login Time: %s\n"), ctime(&qd->last_login_time)); + for (index = 0; index < sizeof(qd->last_login_time) / sizeof(time_t); index++) { + tm_local = localtime(&qd->last_login_time[index]); + g_string_append_printf(info, _("Time: %d-%d-%d, %d:%d:%d
\n"), + (1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday, + tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec); + } + if (qd->last_login_ip.s_addr != 0) { + g_string_append_printf(info, _("IP: %s
\n"), inet_ntoa(qd->last_login_ip)); + } g_string_append(info, ""); @@ -563,6 +717,65 @@ g_string_free(info, TRUE); } +static void action_about_openq(PurplePluginAction *action) +{ + PurpleConnection *gc = (PurpleConnection *) action->context; + qq_data *qd; + GString *info; + gchar *title; + + g_return_if_fail(NULL != gc && NULL != gc->proto_data); + qd = (qq_data *) gc->proto_data; + + info = g_string_new(""); + g_string_append(info, _("

Original Author:
\n")); + g_string_append(info, "puzzlebird
\n"); + g_string_append(info, "
\n"); + g_string_append(info, _("

Code Contributors:
\n")); + g_string_append(info, "gfhuang : patches for libpurple 2.0.0beta2, maintainer
\n"); + g_string_append(info, "Yuan Qingyun : patches for libpurple 1.5.0, maintainer
\n"); + g_string_append(info, "henryouly : file transfer, udp sock5 proxy and qq_show, maintainer
\n"); + g_string_append(info, "hzhr : maintainer
\n"); + g_string_append(info, "joymarquis : maintainer
\n"); + g_string_append(info, "arfankai : fixed bugs in char_conv.c
\n"); + g_string_append(info, "rakescar : provided filter for HTML tag
\n"); + g_string_append(info, "yyw : improved performance on PPC linux
\n"); + g_string_append(info, "lvxiang : provided ip to location original code
\n"); + g_string_append(info, "markhuetsch : OpenQ merge into libpurple, maintainer 2006-2007
\n"); + g_string_append(info, "ccpaging : maintainer since 2007
\n"); + g_string_append(info, "icesky : maintainer since 2007
\n"); + g_string_append(info, "csyfek : faces, maintainer since 2007
\n"); + g_string_append(info, "
\n"); + g_string_append(info, _("

Lovely Patch Writers:
\n")); + g_string_append(info, "gnap : message displaying, documentation
\n"); + g_string_append(info, "manphiz : qun processing
\n"); + g_string_append(info, "moo : qun processing
\n"); + g_string_append(info, "Coly Li : qun processing
\n"); + g_string_append(info, "Emil Alexiev : captcha verification on login based on LumaQQ for MAC (2007), login, add buddy, remove buddy, message exchange and logout
\n"); + g_string_append(info, "
\n"); + g_string_append(info, _("

Acknowledgement:
\n")); + g_string_append(info, "Shufeng Tan : http://sf.net/projects/perl-oicq
\n"); + g_string_append(info, "Jeff Ye : http://www.sinomac.com
\n"); + g_string_append(info, "Hu Zheng : http://forlinux.yeah.net
\n"); + g_string_append(info, "yunfan : http://www.myswear.net
\n"); + g_string_append(info, "OpenQ Team : http://openq.linuxsir.org
\n"); + g_string_append(info, "LumaQQ Team : http://lumaqq.linuxsir.org
\n"); + g_string_append(info, "khc(at)pidgin.im
\n"); + g_string_append(info, "qulogic(at)pidgin.im
\n"); + g_string_append(info, "rlaager(at)pidgin.im
\n"); + g_string_append(info, "OpenQ Google Group : http://groups.google.com/group/openq
\n"); + g_string_append(info, "
\n"); + g_string_append(info, _("

And, all the boys in the backroom...
\n")); + g_string_append(info, _("Feel free to join us! :)")); + g_string_append(info, ""); + + title = g_strdup_printf(_("About OpenQ r%s"), OPENQ_VERSION); + purple_notify_formatted(gc, NULL, title, NULL, info->str, NULL, NULL); + + g_free(title); + g_string_free(info, TRUE); +} + /* static void _qq_menu_search_or_add_permanent_group(PurplePluginAction *action) { @@ -578,35 +791,48 @@ _("Input Qun name here"), _("Only QQ members can create permanent Qun"), "OpenQ", FALSE, FALSE, NULL, - _("Create"), G_CALLBACK(qq_room_create_new), _("Cancel"), NULL, gc); + _("Create"), G_CALLBACK(qq_create_room), _("Cancel"), NULL, gc); } */ -static void _qq_menu_unsubscribe_group(PurpleBlistNode * node) +static void action_chat_quit(PurpleBlistNode * node) { PurpleChat *chat = (PurpleChat *)node; PurpleConnection *gc = purple_account_get_connection(chat->account); GHashTable *components = chat -> components; + gchar *num_str; + guint32 room_id; g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node)); g_return_if_fail(components != NULL); - qq_group_exit(gc, components); + + num_str = g_hash_table_lookup(components, QQ_ROOM_KEY_INTERNAL_ID); + room_id = strtol(num_str, NULL, 10); + g_return_if_fail(room_id != 0); + + qq_room_quit(gc, room_id); } -/* -static void _qq_menu_manage_group(PurpleBlistNode * node) +static void action_chat_get_info(PurpleBlistNode * node) { PurpleChat *chat = (PurpleChat *)node; PurpleConnection *gc = purple_account_get_connection(chat->account); GHashTable *components = chat -> components; + gchar *num_str; + guint32 room_id; g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node)); g_return_if_fail(components != NULL); - qq_group_manage_group(gc, components); + + num_str = g_hash_table_lookup(components, QQ_ROOM_KEY_INTERNAL_ID); + room_id = strtol(num_str, NULL, 10); + g_return_if_fail(room_id != 0); + + qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_INFO, room_id, NULL, 0, + 0, QQ_ROOM_INFO_DISPLAY); } -*/ #if 0 /* TODO: re-enable this */ @@ -614,12 +840,12 @@ { PurpleBuddy *buddy; PurpleConnection *gc; - qq_buddy *q_bud; + qq_buddy_data *bd; g_return_if_fail (PURPLE_BLIST_NODE_IS_BUDDY (node)); buddy = (PurpleBuddy *) node; - q_bud = (qq_buddy *) buddy->proto_data; -/* if (is_online (q_bud->status)) { */ + bd = (qq_buddy_data *) buddy->proto_data; +/* if (is_online (bd->status)) { */ gc = purple_account_get_connection (buddy->account); g_return_if_fail (gc != NULL && gc->proto_data != NULL); qq_send_file(gc, buddy->name, NULL); @@ -628,21 +854,38 @@ #endif /* protocol related menus */ -static GList *_qq_actions(PurplePlugin *plugin, gpointer context) +static GList *qq_actions(PurplePlugin *plugin, gpointer context) { GList *m; PurplePluginAction *act; m = NULL; - act = purple_plugin_action_new(_("Set My Information"), _qq_menu_modify_my_info); + act = purple_plugin_action_new(_("Change Icon"), action_change_icon); + m = g_list_append(m, act); + + act = purple_plugin_action_new(_("Modify Information"), action_modify_info_base); + m = g_list_append(m, act); + + act = purple_plugin_action_new(_("Modify Extend Information"), action_modify_info_ext); + m = g_list_append(m, act); + + act = purple_plugin_action_new(_("Modify Address"), action_modify_info_addr); m = g_list_append(m, act); - act = purple_plugin_action_new(_("Change Password"), _qq_menu_change_password); + act = purple_plugin_action_new(_("Modify Contact"), action_modify_info_contact); + m = g_list_append(m, act); + + act = purple_plugin_action_new(_("Change Password"), action_change_password); m = g_list_append(m, act); - act = purple_plugin_action_new(_("Account Information"), _qq_menu_account_info); + act = purple_plugin_action_new(_("Account Information"), action_show_account_info); m = g_list_append(m, act); + act = purple_plugin_action_new(_("Update all QQ Quns"), action_update_all_rooms); + m = g_list_append(m, act); + + act = purple_plugin_action_new(_("About OpenQ"), action_about_openq); + m = g_list_append(m, act); /* act = purple_plugin_action_new(_("Qun: Search a permanent Qun"), _qq_menu_search_or_add_permanent_group); m = g_list_append(m, act); @@ -654,63 +897,131 @@ return m; } +static void qq_add_buddy_from_menu_cb(PurpleBlistNode *node, gpointer data) +{ + PurpleBuddy *buddy; + PurpleConnection *gc; + + g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); + + buddy = (PurpleBuddy *) node; + gc = purple_account_get_connection(buddy->account); + + qq_add_buddy(gc, buddy, NULL); +} + +static GList *qq_buddy_menu(PurpleBuddy *buddy) +{ + GList *m = NULL; + PurpleMenuAction *act; + /* + PurpleConnection *gc = purple_account_get_connection(buddy->account); + qq_data *qd = gc->proto_data; + */ + qq_buddy_data *bd = (qq_buddy_data *)buddy->proto_data; + + if (bd == NULL) { + act = purple_menu_action_new(_("Add Buddy"), + PURPLE_CALLBACK(qq_add_buddy_from_menu_cb), + NULL, NULL); + m = g_list_append(m, act); + + return m; + + } + +/* TODO : not working, temp commented out by gfhuang */ +#if 0 + if (bd && is_online(bd->status)) { + act = purple_menu_action_new(_("Send File"), PURPLE_CALLBACK(_qq_menu_send_file), NULL, NULL); /* add NULL by gfhuang */ + m = g_list_append(m, act); + } +#endif + return m; +} + /* chat-related (QQ Qun) menu shown up with right-click */ -static GList *_qq_chat_menu(PurpleBlistNode *node) +static GList *qq_chat_menu(PurpleBlistNode *node) { GList *m; PurpleMenuAction *act; m = NULL; - act = purple_menu_action_new(_("Leave the QQ Qun"), PURPLE_CALLBACK(_qq_menu_unsubscribe_group), NULL, NULL); + act = purple_menu_action_new(_("Get Info"), PURPLE_CALLBACK(action_chat_get_info), NULL, NULL); m = g_list_append(m, act); - /* TODO: enable this - act = purple_menu_action_new(_("Show Details"), PURPLE_CALLBACK(_qq_menu_manage_group), NULL, NULL); + act = purple_menu_action_new(_("Quit Qun"), PURPLE_CALLBACK(action_chat_quit), NULL, NULL); m = g_list_append(m, act); - */ - return m; } /* buddy-related menu shown up with right-click */ -static GList *_qq_buddy_menu(PurpleBlistNode * node) +static GList *qq_blist_node_menu(PurpleBlistNode * node) { - GList *m; + if(PURPLE_BLIST_NODE_IS_CHAT(node)) + return qq_chat_menu(node); - if(PURPLE_BLIST_NODE_IS_CHAT(node)) - return _qq_chat_menu(node); + if(PURPLE_BLIST_NODE_IS_BUDDY(node)) + return qq_buddy_menu((PurpleBuddy *) node); - m = NULL; + return NULL; +} -/* TODO : not working, temp commented out by gfhuang */ -#if 0 +/* convert name displayed in a chat channel to original QQ UID */ +static gchar *chat_name_to_purple_name(const gchar *const name) +{ + const char *start; + const char *end; + gchar *ret; + + g_return_val_if_fail(name != NULL, NULL); - act = purple_menu_action_new(_("Block this buddy"), PURPLE_CALLBACK(_qq_menu_block_buddy), NULL, NULL); /* add NULL by gfhuang */ - m = g_list_append(m, act); -/* if (q_bud && is_online(q_bud->status)) { */ - act = purple_menu_action_new(_("Send File"), PURPLE_CALLBACK(_qq_menu_send_file), NULL, NULL); /* add NULL by gfhuang */ - m = g_list_append(m, act); -/* } */ -#endif + /* Sample: (1234567)*/ + start = strchr(name, '('); + g_return_val_if_fail(start != NULL, NULL); + end = strchr(start, ')'); + g_return_val_if_fail(end != NULL && (end - start) > 1, NULL); - return m; + ret = g_strndup(start + 1, end - start - 1); + + return ret; } -/* convert chat nickname to qq-uid to get this buddy info */ +/* convert chat nickname to uid to get this buddy info */ /* who is the nickname of buddy in QQ chat-room (Qun) */ -static void _qq_get_chat_buddy_info(PurpleConnection *gc, gint channel, const gchar *who) +static void qq_get_chat_buddy_info(PurpleConnection *gc, gint channel, const gchar *who) { - gchar *purple_name; + qq_data *qd; + gchar *uid_str; + guint32 uid; + + purple_debug_info("QQ", "Get chat buddy info of %s\n", who); g_return_if_fail(who != NULL); - purple_name = chat_name_to_purple_name(who); - if (purple_name != NULL) - _qq_get_info(gc, purple_name); + uid_str = chat_name_to_purple_name(who); + if (uid_str == NULL) { + return; + } + + qd = gc->proto_data; + uid = purple_name_to_uid(uid_str); + g_free(uid_str); + + if (uid <= 0) { + purple_debug_error("QQ", "Not valid chat name: %s\n", who); + purple_notify_error(gc, NULL, _("Invalid name"), NULL); + return; + } + + if (qd->client_version < 2007) { + qq_request_get_level(gc, uid); + } + qq_request_buddy_info(gc, uid, 0, QQ_BUDDY_INFO_DISPLAY); } -/* convert chat nickname to qq-uid to invite individual IM to buddy */ +/* convert chat nickname to uid to invite individual IM to buddy */ /* who is the nickname of buddy in QQ chat-room (Qun) */ -static gchar *_qq_get_chat_buddy_real_name(PurpleConnection *gc, gint channel, const gchar *who) +static gchar *qq_get_chat_buddy_real_name(PurpleConnection *gc, gint channel, const gchar *who) { g_return_val_if_fail(who != NULL, NULL); return chat_name_to_purple_name(who); @@ -722,21 +1033,21 @@ NULL, /* user_splits */ NULL, /* protocol_options */ {"png", 96, 96, 96, 96, 0, PURPLE_ICON_SCALE_SEND}, /* icon_spec */ - _qq_list_icon, /* list_icon */ - _qq_list_emblem, /* list_emblems */ - _qq_status_text, /* status_text */ - _qq_tooltip_text, /* tooltip_text */ - _qq_away_states, /* away_states */ - _qq_buddy_menu, /* blist_node_menu */ + qq_list_icon, /* list_icon */ + qq_list_emblem, /* list_emblems */ + qq_status_text, /* status_text */ + qq_tooltip_text, /* tooltip_text */ + qq_status_types, /* away_states */ + qq_blist_node_menu, /* blist_node_menu */ qq_chat_info, /* chat_info */ qq_chat_info_defaults, /* chat_info_defaults */ qq_login, /* open */ qq_close, /* close */ - _qq_send_im, /* send_im */ + qq_send_im, /* send_im */ NULL, /* set_info */ NULL, /* send_typing */ - _qq_get_info, /* get_info */ - _qq_change_status, /* change status */ + qq_show_buddy_info, /* get_info */ + qq_change_status, /* change status */ NULL, /* set_idle */ NULL, /* change_passwd */ qq_add_buddy, /* add_buddy */ @@ -744,30 +1055,30 @@ qq_remove_buddy, /* remove_buddy */ NULL, /* remove_buddies */ NULL, /* add_permit */ - NULL, /* add_deny */ + qq_add_deny, /* add_deny */ NULL, /* rem_permit */ NULL, /* rem_deny */ - NULL, /* set_permit_deny */ + qq_set_permit_deny, /* set_permit_deny */ qq_group_join, /* join_chat */ NULL, /* reject chat invite */ NULL, /* get_chat_name */ NULL, /* chat_invite */ NULL, /* chat_leave */ NULL, /* chat_whisper */ - _qq_chat_send, /* chat_send */ + qq_chat_send, /* chat_send */ NULL, /* keepalive */ NULL, /* register_user */ - _qq_get_chat_buddy_info, /* get_cb_info */ + qq_get_chat_buddy_info, /* get_cb_info */ NULL, /* get_cb_away */ NULL, /* alias_buddy */ - NULL, /* group_buddy */ + NULL, /* change buddy's group */ NULL, /* rename_group */ NULL, /* buddy_free */ NULL, /* convo_closed */ NULL, /* normalize */ - qq_set_my_buddy_icon, /* set_buddy_icon */ + qq_set_custom_icon, NULL, /* remove_group */ - _qq_get_chat_buddy_real_name, /* get_cb_real_name */ + qq_get_chat_buddy_real_name, /* get_cb_real_name */ NULL, /* set_chat_topic */ NULL, /* find_blist_chat */ qq_roomlist_get_list, /* roomlist_get_list */ @@ -815,7 +1126,7 @@ NULL, /**< ui_info */ &prpl_info, /**< extra_info */ NULL, /**< prefs_info */ - _qq_actions, + qq_actions, /* padding */ NULL, @@ -829,35 +1140,60 @@ { PurpleAccountOption *option; PurpleKeyValuePair *kvp; - GList *list = NULL; - GList *kvlist = NULL; - GList *entry; + GList *server_list = NULL; + GList *server_kv_list = NULL; + GList *it; +//#ifdef DEBUG + GList *version_kv_list = NULL; +//#endif - list = server_list_build('A'); + server_list = server_list_build('A'); - purple_prefs_add_string_list("/plugins/prpl/qq/serverlist", list); - list = purple_prefs_get_string_list("/plugins/prpl/qq/serverlist"); + purple_prefs_add_string_list("/plugins/prpl/qq/serverlist", server_list); + server_list = purple_prefs_get_string_list("/plugins/prpl/qq/serverlist"); - kvlist = NULL; + server_kv_list = NULL; kvp = g_new0(PurpleKeyValuePair, 1); kvp->key = g_strdup(_("Auto")); kvp->value = g_strdup("auto"); - kvlist = g_list_append(kvlist, kvp); + server_kv_list = g_list_append(server_kv_list, kvp); - entry = list; - while(entry) { - if (entry->data != NULL && strlen(entry->data) > 0) { + it = server_list; + while(it) { + if (it->data != NULL && strlen(it->data) > 0) { kvp = g_new0(PurpleKeyValuePair, 1); - kvp->key = g_strdup(entry->data); - kvp->value = g_strdup(entry->data); - kvlist = g_list_append(kvlist, kvp); + kvp->key = g_strdup(it->data); + kvp->value = g_strdup(it->data); + server_kv_list = g_list_append(server_kv_list, kvp); } - entry = entry->next; + it = it->next; } - option = purple_account_option_list_new(_("Server"), "server", kvlist); + g_list_free(server_list); + + option = purple_account_option_list_new(_("Select Server"), "server", server_kv_list); prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); +//#ifdef DEBUG + kvp = g_new0(PurpleKeyValuePair, 1); + kvp->key = g_strdup(_("QQ2005")); + kvp->value = g_strdup("qq2005"); + version_kv_list = g_list_append(version_kv_list, kvp); + + kvp = g_new0(PurpleKeyValuePair, 1); + kvp->key = g_strdup(_("QQ2007")); + kvp->value = g_strdup("qq2007"); + version_kv_list = g_list_append(version_kv_list, kvp); + + kvp = g_new0(PurpleKeyValuePair, 1); + kvp->key = g_strdup(_("QQ2008")); + kvp->value = g_strdup("qq2008"); + version_kv_list = g_list_append(version_kv_list, kvp); + + option = purple_account_option_list_new(_("Client Version"), "client_version", version_kv_list); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); +//#endif + option = purple_account_option_bool_new(_("Connect by TCP"), "use_tcp", TRUE); prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); @@ -876,9 +1212,10 @@ purple_prefs_add_none("/plugins/prpl/qq"); purple_prefs_add_bool("/plugins/prpl/qq/show_status_by_icon", TRUE); purple_prefs_add_bool("/plugins/prpl/qq/show_fake_video", FALSE); - purple_prefs_add_bool("/plugins/prpl/qq/show_room_when_newin", TRUE); + purple_prefs_add_bool("/plugins/prpl/qq/auto_popup_conversation", FALSE); + purple_prefs_add_bool("/plugins/prpl/qq/auto_get_authorize_info", TRUE); purple_prefs_add_int("/plugins/prpl/qq/resend_interval", 3); - purple_prefs_add_int("/plugins/prpl/qq/resend_times", 4); + purple_prefs_add_int("/plugins/prpl/qq/resend_times", 10); } PURPLE_INIT_PLUGIN(qq, init_plugin, info); diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/qq.h --- a/libpurple/protocols/qq/qq.h Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/qq.h Wed Oct 29 21:38:53 2008 +0000 @@ -37,9 +37,34 @@ #define QQ_KEY_LENGTH 16 typedef struct _qq_data qq_data; -typedef struct _qq_buddy qq_buddy; +typedef struct _qq_buddy_data qq_buddy_data; typedef struct _qq_interval qq_interval; typedef struct _qq_net_stat qq_net_stat; +typedef struct _qq_login_data qq_login_data; +typedef struct _qq_captcha_data qq_captcha_data; + +struct _qq_captcha_data { + guint8 *token; + guint16 token_len; + guint8 next_index; + guint8 *data; + guint16 data_len; +}; + +struct _qq_login_data { + guint8 random_key[QQ_KEY_LENGTH]; /* first encrypt key generated by client */ + guint8 *token; /* get from server */ + guint8 token_len; + guint8 *token_ex; /* get from server */ + guint16 token_ex_len; + + guint8 pwd_md5[QQ_KEY_LENGTH]; /* password in md5 (or md5' md5) */ + guint8 pwd_twice_md5[QQ_KEY_LENGTH]; + + guint8 *login_token; + guint16 login_token_len; + guint8 login_key[QQ_KEY_LENGTH]; +}; struct _qq_interval { gint resend; @@ -55,7 +80,7 @@ glong rcved_dup; }; -struct _qq_buddy { +struct _qq_buddy_data { guint32 uid; guint16 face; /* index: 0 - 299 */ guint8 age; @@ -66,7 +91,7 @@ guint8 status; guint8 ext_flag; guint8 comm_flag; /* details in qq_buddy_list.c */ - guint16 client_version; + guint16 client_tag; guint8 onlineTime; guint16 level; guint16 timeRemainder; @@ -105,8 +130,14 @@ GList *servers; gchar *curr_server; /* point to servers->data, do not free*/ + guint16 client_tag; + gint client_version; + struct in_addr redirect_ip; guint16 redirect_port; + guint8 *redirect; + guint8 redirect_len; + guint check_watcher; guint connect_watcher; gint connect_retry; @@ -119,46 +150,35 @@ GList *transactions; /* check ack packet and resend */ guint32 uid; /* QQ number */ - guint8 *token; /* get from server*/ - int token_len; - guint8 inikey[QQ_KEY_LENGTH]; /* initial key to encrypt login packet */ - guint8 password_twice_md5[QQ_KEY_LENGTH]; /* password in md5 (or md5' md5) */ + + qq_login_data ld; + qq_captcha_data captcha; + guint8 session_key[QQ_KEY_LENGTH]; /* later use this as key in this session */ guint8 session_md5[QQ_KEY_LENGTH]; /* concatenate my uid with session_key and md5 it */ guint16 send_seq; /* send sequence number */ guint8 login_mode; /* online of invisible */ - gboolean is_login; /* used by qq-add_buddy */ + gboolean is_login; /* used by qq_add_buddy */ PurpleXfer *xfer; /* file transfer handler */ /* get from login reply packet */ + struct in_addr my_local_ip; /* my local ip address detected by server */ + guint16 my_local_port; /* my lcoal port detected by server */ time_t login_time; - time_t last_login_time; - gchar *last_login_ip; + time_t last_login_time[3]; + struct in_addr last_login_ip; /* get from keep_alive packet */ struct in_addr my_ip; /* my ip address detected by server */ guint16 my_port; /* my port detected by server */ guint16 my_icon; /* my icon index */ - guint16 my_level; /* my level */ - guint32 total_online; /* the number of online QQ users */ - time_t last_get_online; /* last time send get_friends_online packet */ + guint32 online_total; /* the number of online QQ users */ + time_t online_last_update; /* last time send get_friends_online packet */ PurpleRoomlist *roomlist; - gint channel; /* the id for opened chat conversation */ GList *groups; - GSList *joining_groups; - GSList *adding_groups_from_server; /* internal ids of groups the server wants in my blist */ - GList *buddies; - GList *contact_info_window; - GList *group_info_window; - GList *info_query; - GList *add_buddy_request; - - /* TODO pass qq_send_packet_get_info() a callback and use signals to get rid of these */ - gboolean modifying_info; - gboolean modifying_face; gboolean is_show_notice; gboolean is_show_news; diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/qq_base.c --- a/libpurple/protocols/qq/qq_base.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/qq_base.c Wed Oct 29 21:38:53 2008 +0000 @@ -26,22 +26,335 @@ #include "internal.h" #include "server.h" #include "cipher.h" +#include "request.h" #include "buddy_info.h" #include "buddy_list.h" #include "char_conv.h" #include "qq_crypt.h" #include "group.h" -#include "header_info.h" +#include "qq_define.h" +#include "qq_network.h" #include "qq_base.h" #include "packet_parse.h" #include "qq.h" #include "qq_network.h" #include "utils.h" -#define QQ_LOGIN_DATA_LENGTH 416 -#define QQ_LOGIN_REPLY_OK_PACKET_LEN 139 -#define QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN 11 +/* generate a md5 key using uid and session_key */ +static void get_session_md5(guint8 *session_md5, guint32 uid, guint8 *session_key) +{ + guint8 src[QQ_KEY_LENGTH + QQ_KEY_LENGTH]; + gint bytes = 0; + + bytes += qq_put32(src + bytes, uid); + bytes += qq_putdata(src + bytes, session_key, QQ_KEY_LENGTH); + + qq_get_md5(session_md5, QQ_KEY_LENGTH, src, bytes); +} + +/* process login reply which says OK */ +static gint8 process_login_ok(PurpleConnection *gc, guint8 *data, gint len) +{ + qq_data *qd; + gint bytes; + + guint8 ret; + guint32 uid; + struct in_addr ip; + guint16 port; + struct tm *tm_local; + + qd = (qq_data *) gc->proto_data; + /* qq_show_packet("Login reply", data, len); */ + + if (len < 139) { + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, + _("Can not decrypt get server reply")); + return QQ_LOGIN_REPLY_ERR; + } + + bytes = 0; + bytes += qq_get8(&ret, data + bytes); + bytes += qq_getdata(qd->session_key, sizeof(qd->session_key), data + bytes); + get_session_md5(qd->session_md5, qd->uid, qd->session_key); + purple_debug_info("QQ", "Got session_key\n"); + bytes += qq_get32(&uid, data + bytes); + if (uid != qd->uid) { + purple_debug_warning("QQ", "My uid in login reply is %d, not %d\n", uid, qd->uid); + } + bytes += qq_getIP(&qd->my_ip, data + bytes); + bytes += qq_get16(&qd->my_port, data + bytes); + purple_debug_info("QQ", "Internet IP: %s, %d\n", inet_ntoa(qd->my_ip), qd->my_port); + + bytes += qq_getIP(&qd->my_local_ip, data + bytes); + bytes += qq_get16(&qd->my_local_port, data + bytes); + purple_debug_info("QQ", "Local IP: %s, %d\n", inet_ntoa(qd->my_local_ip), qd->my_local_port); + + bytes += qq_getime(&qd->login_time, data + bytes); + tm_local = localtime(&qd->login_time); + purple_debug_info("QQ", "Login time: %d-%d-%d, %d:%d:%d\n", + (1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday, + tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec); + /* skip unknown 2 bytes, 0x(03 0a) */ + bytes += 2; + /* skip unknown 24 bytes, maybe token to access Qun shared files */ + bytes += 24; + /* unknow ip and port */ + bytes += qq_getIP(&ip, data + bytes); + bytes += qq_get16(&port, data + bytes); + purple_debug_info("QQ", "Unknow IP: %s, %d\n", inet_ntoa(ip), port); + /* unknow ip and port */ + bytes += qq_getIP(&ip, data + bytes); + bytes += qq_get16(&port, data + bytes); + purple_debug_info("QQ", "Unknow IP: %s, %d\n", inet_ntoa(ip), port); + /* unknown 4 bytes, 0x(00 81 00 00)*/ + bytes += 4; + /* skip unknown 32 bytes, maybe key to access QQ Home */ + bytes += 32; + /* skip unknown 16 bytes, 0x(00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 00) */ + bytes += 16; + /* time */ + bytes += qq_getime(&qd->last_login_time[0], data + bytes); + tm_local = localtime(&qd->last_login_time[0]); + purple_debug_info("QQ", "Last login time: %d-%d-%d, %d:%d:%d\n", + (1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday, + tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec); + /* unknow time */ + g_return_val_if_fail(sizeof(qd->last_login_time) / sizeof(time_t) > 1, QQ_LOGIN_REPLY_OK); + bytes += qq_getime(&qd->last_login_time[1], data + bytes); + tm_local = localtime(&qd->last_login_time[1]); + purple_debug_info("QQ", "Time: %d-%d-%d, %d:%d:%d\n", + (1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday, + tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec); + + g_return_val_if_fail(sizeof(qd->last_login_time) / sizeof(time_t) > 2, QQ_LOGIN_REPLY_OK); + bytes += qq_getime(&qd->last_login_time[2], data + bytes); + tm_local = localtime(&qd->last_login_time[2]); + purple_debug_info("QQ", "Time: %d-%d-%d, %d:%d:%d\n", + (1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday, + tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec); + /* unknow 9 bytes, 0x(00 0a 00 0a 01 00 00 0e 10) */ + + if (len > 139) { + purple_debug_warning("QQ", "Login reply more than expected %d bytes, read %d bytes\n", 139, bytes); + } + return QQ_LOGIN_REPLY_OK; +} + +/* process login reply packet which includes redirected new server address */ +static gint8 process_login_redirect(PurpleConnection *gc, guint8 *data, gint len) +{ + qq_data *qd; + gint bytes; + struct { + guint8 result; + guint32 uid; + struct in_addr new_server_ip; + guint16 new_server_port; + } packet; + + + if (len < 11) { + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, + _("Can not decrypt get server reply")); + return QQ_LOGIN_REPLY_ERR; + } + + qd = (qq_data *) gc->proto_data; + bytes = 0; + /* 000-000: reply code */ + bytes += qq_get8(&packet.result, data + bytes); + /* 001-004: login uid */ + bytes += qq_get32(&packet.uid, data + bytes); + /* 005-008: redirected new server IP */ + bytes += qq_getIP(&packet.new_server_ip, data + bytes); + /* 009-010: redirected new server port */ + bytes += qq_get16(&packet.new_server_port, data + bytes); + + if (len > 11) { + purple_debug_error("QQ", "Login redirect more than expected %d bytes, read %d bytes\n", 11, bytes); + } + + /* redirect to new server, do not disconnect or connect here + * those connect should be called at packet_process */ + qd->redirect_ip.s_addr = packet.new_server_ip.s_addr; + qd->redirect_port = packet.new_server_port; + return QQ_LOGIN_REPLY_REDIRECT; +} + +/* request before login */ +void qq_request_token(PurpleConnection *gc) +{ + qq_data *qd; + guint8 buf[16] = {0}; + gint bytes = 0; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + + bytes += qq_put8(buf + bytes, 0); + + qd->send_seq++; + qq_send_cmd_encrypted(gc, QQ_CMD_TOKEN, qd->send_seq, buf, bytes, TRUE); +} + +/* send login packet to QQ server */ +void qq_request_login(PurpleConnection *gc) +{ + qq_data *qd; + guint8 *buf, *raw_data; + gint bytes; + guint8 *encrypted; + gint encrypted_len; + + /* for QQ 2005? copy from lumaqq */ + static const guint8 login_23_51[29] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x86, 0xcc, 0x4c, 0x35, + 0x2c, 0xd3, 0x73, 0x6c, 0x14, 0xf6, 0xf6, 0xaf, + 0xc3, 0xfa, 0x33, 0xa4, 0x01 + }; + + static const guint8 login_53_68[16] = { + 0x8D, 0x8B, 0xFA, 0xEC, 0xD5, 0x52, 0x17, 0x4A, + 0x86, 0xF9, 0xA7, 0x75, 0xE6, 0x32, 0xD1, 0x6D + }; + + static const guint8 login_100_bytes[100] = { + 0x40, 0x0B, 0x04, 0x02, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0xE9, 0x03, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF3, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xED, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xEC, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0xEE, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0xEF, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0xEB, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + + g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0); + + raw_data = g_newa(guint8, MAX_PACKET_SIZE - 16); + memset(raw_data, 0, MAX_PACKET_SIZE - 16); + + encrypted = g_newa(guint8, MAX_PACKET_SIZE); /* 16 bytes more */ + + bytes = 0; + /* now generate the encrypted data + * 000-015 use password_twice_md5 as key to encrypt empty string */ + encrypted_len = qq_encrypt(encrypted, (guint8 *) "", 0, qd->ld.pwd_twice_md5); + g_return_if_fail(encrypted_len == 16); + bytes += qq_putdata(raw_data + bytes, encrypted, encrypted_len); + + /* 016-016 */ + bytes += qq_put8(raw_data + bytes, 0x00); + /* 017-020, used to be IP, now zero */ + bytes += qq_put32(raw_data + bytes, 0x00000000); + /* 021-022, used to be port, now zero */ + bytes += qq_put16(raw_data + bytes, 0x0000); + /* 023-051, fixed value, unknown */ + bytes += qq_putdata(raw_data + bytes, login_23_51, 29); + /* 052-052, login mode */ + bytes += qq_put8(raw_data + bytes, qd->login_mode); + /* 053-068, fixed value, maybe related to per machine */ + bytes += qq_putdata(raw_data + bytes, login_53_68, 16); + /* 069, login token length */ + bytes += qq_put8(raw_data + bytes, qd->ld.token_len); + /* 070-093, login token, normally 24 bytes */ + bytes += qq_putdata(raw_data + bytes, qd->ld.token, qd->ld.token_len); + /* 100 bytes unknown */ + bytes += qq_putdata(raw_data + bytes, login_100_bytes, 100); + /* all zero left */ + memset(raw_data + bytes, 0, 416 - bytes); + bytes = 416; + + encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.random_key); + + buf = g_newa(guint8, MAX_PACKET_SIZE); + memset(buf, 0, MAX_PACKET_SIZE); + bytes = 0; + bytes += qq_putdata(buf + bytes, qd->ld.random_key, QQ_KEY_LENGTH); + bytes += qq_putdata(buf + bytes, encrypted, encrypted_len); + + qd->send_seq++; + qq_send_cmd_encrypted(gc, QQ_CMD_LOGIN, qd->send_seq, buf, bytes, TRUE); +} + +guint8 qq_process_token(PurpleConnection *gc, guint8 *buf, gint buf_len) +{ + qq_data *qd; + gint bytes; + guint8 ret; + guint8 token_len; + gchar *msg; + + g_return_val_if_fail(buf != NULL && buf_len != 0, QQ_LOGIN_REPLY_ERR); + + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, QQ_LOGIN_REPLY_ERR); + qd = (qq_data *) gc->proto_data; + + bytes = 0; + bytes += qq_get8(&ret, buf + bytes); + bytes += qq_get8(&token_len, buf + bytes); + + if (ret != QQ_LOGIN_REPLY_OK) { + qq_show_packet("Failed requesting token", buf, buf_len); + + msg = g_strdup_printf( _("Failed requesting token, 0x%02X"), ret ); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, + msg); + g_free(msg); + return QQ_LOGIN_REPLY_ERR; + } + + if (bytes + token_len < buf_len) { + msg = g_strdup_printf( _("Invalid token len, %d"), token_len); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, + msg); + g_free(msg); + return QQ_LOGIN_REPLY_ERR; + } + + if (bytes + token_len > buf_len) { + purple_debug_info("QQ", "Extra token data, %d %d\n", token_len, buf_len - bytes); + } + qq_show_packet("Got token", buf + bytes, buf_len - bytes); + + if (qd->ld.token != NULL) { + g_free(qd->ld.token); + qd->ld.token = NULL; + qd->ld.token_len = 0; + } + qd->ld.token = g_new0(guint8, token_len); + qd->ld.token_len = token_len; + g_memmove(qd->ld.token, buf + 2, qd->ld.token_len); + return ret; +} + +/* send logout packets to QQ server */ +void qq_request_logout(PurpleConnection *gc) +{ + gint i; + qq_data *qd; + + qd = (qq_data *) gc->proto_data; + for (i = 0; i < 4; i++) + qq_send_cmd(gc, QQ_CMD_LOGOUT, qd->ld.pwd_twice_md5, QQ_KEY_LENGTH); + + qd->is_login = FALSE; /* update login status AFTER sending logout packets */ +} /* for QQ 2003iii 0117, fixed value */ /* static const guint8 login_23_51[29] = { @@ -61,37 +374,6 @@ }; */ -/* for QQ 2005? copy from lumaqq */ -/* FIXME: change to guint8 */ -static const guint8 login_23_51[29] = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x86, 0xcc, 0x4c, 0x35, - 0x2c, 0xd3, 0x73, 0x6c, 0x14, 0xf6, 0xf6, 0xaf, - 0xc3, 0xfa, 0x33, 0xa4, 0x01 -}; - -static const guint8 login_53_68[16] = { - 0x8D, 0x8B, 0xFA, 0xEC, 0xD5, 0x52, 0x17, 0x4A, - 0x86, 0xF9, 0xA7, 0x75, 0xE6, 0x32, 0xD1, 0x6D -}; - -static const guint8 login_100_bytes[100] = { - 0x40, 0x0B, 0x04, 0x02, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, 0x09, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xE9, 0x03, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF3, 0x03, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xED, - 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0xEC, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x03, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xEE, 0x03, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0xEF, 0x03, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0xEB, 0x03, 0x00, - 0x00, 0x00, 0x00, 0x00 -}; - - /* fixed value, not affected by version, or mac address */ /* static const guint8 login_53_68[16] = { @@ -100,310 +382,16 @@ }; */ - -typedef struct _qq_login_reply_ok qq_login_reply_ok_packet; -typedef struct _qq_login_reply_redirect qq_login_reply_redirect_packet; - -struct _qq_login_reply_ok { - guint8 result; - guint8 session_key[QQ_KEY_LENGTH]; - guint32 uid; - struct in_addr client_ip; /* those detected by server */ - guint16 client_port; - struct in_addr server_ip; - guint16 server_port; - time_t login_time; - guint8 unknown1[26]; - struct in_addr unknown_server1_ip; - guint16 unknown_server1_port; - struct in_addr unknown_server2_ip; - guint16 unknown_server2_port; - guint16 unknown2; /* 0x0001 */ - guint16 unknown3; /* 0x0000 */ - guint8 unknown4[32]; - guint8 unknown5[12]; - struct in_addr last_client_ip; - time_t last_login_time; - guint8 unknown6[8]; -}; - -struct _qq_login_reply_redirect { - guint8 result; - guint32 uid; - struct in_addr new_server_ip; - guint16 new_server_port; -}; - -/* generate a md5 key using uid and session_key */ -static void get_session_md5(guint8 *session_md5, guint32 uid, guint8 *session_key) -{ - guint8 src[QQ_KEY_LENGTH + QQ_KEY_LENGTH]; - gint bytes = 0; - - bytes += qq_put32(src + bytes, uid); - bytes += qq_putdata(src + bytes, session_key, QQ_KEY_LENGTH); - - qq_get_md5(session_md5, QQ_KEY_LENGTH, src, bytes); -} - -/* process login reply which says OK */ -static gint8 process_login_ok(PurpleConnection *gc, guint8 *data, gint len) -{ - gint bytes; - qq_data *qd; - qq_login_reply_ok_packet lrop; - - qd = (qq_data *) gc->proto_data; - /* FIXME, check QQ_LOGIN_REPLY_OK_PACKET_LEN here */ - bytes = 0; - - /* 000-000: reply code */ - bytes += qq_get8(&lrop.result, data + bytes); - /* 001-016: session key */ - bytes += qq_getdata(lrop.session_key, sizeof(lrop.session_key), data + bytes); - purple_debug_info("QQ", "Got session_key\n"); - /* 017-020: login uid */ - bytes += qq_get32(&lrop.uid, data + bytes); - /* 021-024: server detected user public IP */ - bytes += qq_getIP(&lrop.client_ip, data + bytes); - /* 025-026: server detected user port */ - bytes += qq_get16(&lrop.client_port, data + bytes); - /* 027-030: server detected itself ip 127.0.0.1 ? */ - bytes += qq_getIP(&lrop.server_ip, data + bytes); - /* 031-032: server listening port */ - bytes += qq_get16(&lrop.server_port, data + bytes); - /* 033-036: login time for current session */ - bytes += qq_getime(&lrop.login_time, data + bytes); - /* 037-062: 26 bytes, unknown */ - bytes += qq_getdata((guint8 *) &lrop.unknown1, 26, data + bytes); - /* 063-066: unknown server1 ip address */ - bytes += qq_getIP(&lrop.unknown_server1_ip, data + bytes); - /* 067-068: unknown server1 port */ - bytes += qq_get16(&lrop.unknown_server1_port, data + bytes); - /* 069-072: unknown server2 ip address */ - bytes += qq_getIP(&lrop.unknown_server2_ip, data + bytes); - /* 073-074: unknown server2 port */ - bytes += qq_get16(&lrop.unknown_server2_port, data + bytes); - /* 075-076: 2 bytes unknown */ - bytes += qq_get16(&lrop.unknown2, data + bytes); - /* 077-078: 2 bytes unknown */ - bytes += qq_get16(&lrop.unknown3, data + bytes); - /* 079-110: 32 bytes unknown */ - bytes += qq_getdata((guint8 *) &lrop.unknown4, 32, data + bytes); - /* 111-122: 12 bytes unknown */ - bytes += qq_getdata((guint8 *) &lrop.unknown5, 12, data + bytes); - /* 123-126: login IP of last session */ - bytes += qq_getIP(&lrop.last_client_ip, data + bytes); - /* 127-130: login time of last session */ - bytes += qq_getime(&lrop.last_login_time, data + bytes); - /* 131-138: 8 bytes unknown */ - bytes += qq_getdata((guint8 *) &lrop.unknown6, 8, data + bytes); - - if (bytes != QQ_LOGIN_REPLY_OK_PACKET_LEN) { /* fail parsing login info */ - purple_debug_warning("QQ", - "Fail parsing login info, expect %d bytes, read %d bytes\n", - QQ_LOGIN_REPLY_OK_PACKET_LEN, bytes); - } /* but we still go on as login OK */ - - memcpy(qd->session_key, lrop.session_key, sizeof(qd->session_key)); - get_session_md5(qd->session_md5, qd->uid, qd->session_key); - - qd->my_ip.s_addr = lrop.client_ip.s_addr; - - qd->my_port = lrop.client_port; - qd->login_time = lrop.login_time; - qd->last_login_time = lrop.last_login_time; - qd->last_login_ip = g_strdup( inet_ntoa(lrop.last_client_ip) ); - - return QQ_LOGIN_REPLY_OK; -} - -/* process login reply packet which includes redirected new server address */ -static gint8 process_login_redirect(PurpleConnection *gc, guint8 *data, gint len) -{ - qq_data *qd; - gint bytes; - qq_login_reply_redirect_packet lrrp; - - qd = (qq_data *) gc->proto_data; - bytes = 0; - /* 000-000: reply code */ - bytes += qq_get8(&lrrp.result, data + bytes); - /* 001-004: login uid */ - bytes += qq_get32(&lrrp.uid, data + bytes); - /* 005-008: redirected new server IP */ - bytes += qq_getIP(&lrrp.new_server_ip, data + bytes); - /* 009-010: redirected new server port */ - bytes += qq_get16(&lrrp.new_server_port, data + bytes); - - if (bytes != QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN) { - purple_debug_error("QQ", - "Fail parsing login redirect packet, expect %d bytes, read %d bytes\n", - QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN, bytes); - return QQ_LOGIN_REPLY_ERR_MISC; - } - - /* redirect to new server, do not disconnect or connect here - * those connect should be called at packet_process */ - qd->redirect_ip.s_addr = lrrp.new_server_ip.s_addr; - qd->redirect_port = lrrp.new_server_port; - return QQ_LOGIN_REPLY_REDIRECT; -} - -/* request before login */ -void qq_send_packet_token(PurpleConnection *gc) -{ - qq_data *qd; - guint8 buf[16] = {0}; - gint bytes = 0; - - g_return_if_fail(gc != NULL && gc->proto_data != NULL); - qd = (qq_data *) gc->proto_data; - - bytes += qq_put8(buf + bytes, 0); - - qd->send_seq++; - qq_send_cmd_encrypted(gc, QQ_CMD_TOKEN, qd->send_seq, buf, bytes, TRUE); -} - -/* send login packet to QQ server */ -void qq_send_packet_login(PurpleConnection *gc) -{ - qq_data *qd; - guint8 *buf, *raw_data; - gint bytes; - guint8 *encrypted_data; - gint encrypted_len; - - g_return_if_fail(gc != NULL && gc->proto_data != NULL); - qd = (qq_data *) gc->proto_data; - - g_return_if_fail(qd->token != NULL && qd->token_len > 0); - -#ifdef DEBUG - memset(qd->inikey, 0x01, sizeof(qd->inikey)); -#else - for (bytes = 0; bytes < sizeof(qd->inikey); bytes++) { - qd->inikey[bytes] = (guint8) (rand() & 0xff); - } -#endif - - raw_data = g_newa(guint8, QQ_LOGIN_DATA_LENGTH); - memset(raw_data, 0, QQ_LOGIN_DATA_LENGTH); - - encrypted_data = g_newa(guint8, QQ_LOGIN_DATA_LENGTH + 16); /* 16 bytes more */ - - bytes = 0; - /* now generate the encrypted data - * 000-015 use password_twice_md5 as key to encrypt empty string */ - encrypted_len = qq_encrypt(raw_data + bytes, (guint8 *) "", 0, qd->password_twice_md5); - g_return_if_fail(encrypted_len == 16); - bytes += encrypted_len; - - /* 016-016 */ - bytes += qq_put8(raw_data + bytes, 0x00); - /* 017-020, used to be IP, now zero */ - bytes += qq_put32(raw_data + bytes, 0x00000000); - /* 021-022, used to be port, now zero */ - bytes += qq_put16(raw_data + bytes, 0x0000); - /* 023-051, fixed value, unknown */ - bytes += qq_putdata(raw_data + bytes, login_23_51, 29); - /* 052-052, login mode */ - bytes += qq_put8(raw_data + bytes, qd->login_mode); - /* 053-068, fixed value, maybe related to per machine */ - bytes += qq_putdata(raw_data + bytes, login_53_68, 16); - /* 069, login token length */ - bytes += qq_put8(raw_data + bytes, qd->token_len); - /* 070-093, login token, normally 24 bytes */ - bytes += qq_putdata(raw_data + bytes, qd->token, qd->token_len); - /* 100 bytes unknown */ - bytes += qq_putdata(raw_data + bytes, login_100_bytes, 100); - /* all zero left */ - - encrypted_len = qq_encrypt(encrypted_data, raw_data, QQ_LOGIN_DATA_LENGTH, qd->inikey); - - buf = g_newa(guint8, MAX_PACKET_SIZE); - memset(buf, 0, MAX_PACKET_SIZE); - bytes = 0; - bytes += qq_putdata(buf + bytes, qd->inikey, QQ_KEY_LENGTH); - bytes += qq_putdata(buf + bytes, encrypted_data, encrypted_len); - - qd->send_seq++; - qq_send_cmd_encrypted(gc, QQ_CMD_LOGIN, qd->send_seq, buf, bytes, TRUE); -} - -guint8 qq_process_token_reply(PurpleConnection *gc, guint8 *buf, gint buf_len) -{ - qq_data *qd; - guint8 ret; - int token_len; - gchar *error_msg; - - g_return_val_if_fail(buf != NULL && buf_len != 0, -1); - - g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1); - qd = (qq_data *) gc->proto_data; - - ret = buf[0]; - - if (ret != QQ_TOKEN_REPLY_OK) { - purple_debug_error("QQ", "Failed to request token: %d\n", buf[0]); - qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ", - buf, buf_len, - ">>> [default] decrypt and dump"); - error_msg = try_dump_as_gbk(buf, buf_len); - if (error_msg == NULL) { - error_msg = g_strdup_printf( _("Invalid token reply code, 0x%02X"), ret); - } - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_msg); - g_free(error_msg); - return ret; - } - - token_len = buf_len-2; - if (token_len <= 0) { - error_msg = g_strdup_printf( _("Invalid token len, %d"), token_len); - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_msg); - g_free(error_msg); - return -1; - } - - if (buf[1] != token_len) { - purple_debug_info("QQ", - "Invalid token len. Packet specifies length of %d, actual length is %d\n", buf[1], buf_len-2); - } - qq_hex_dump(PURPLE_DEBUG_INFO, "QQ", - buf+2, token_len, - "<<< got a token -> [default] decrypt and dump"); - - qd->token = g_new0(guint8, token_len); - qd->token_len = token_len; - g_memmove(qd->token, buf + 2, qd->token_len); - return ret; -} - -/* send logout packets to QQ server */ -void qq_send_packet_logout(PurpleConnection *gc) -{ - gint i; - qq_data *qd; - - qd = (qq_data *) gc->proto_data; - for (i = 0; i < 4; i++) - qq_send_cmd(gc, QQ_CMD_LOGOUT, qd->password_twice_md5, QQ_KEY_LENGTH); - - qd->is_login = FALSE; /* update login status AFTER sending logout packets */ -} - /* process the login reply packet */ -guint8 qq_process_login_reply( PurpleConnection *gc, guint8 *data, gint data_len) +guint8 qq_process_login( PurpleConnection *gc, guint8 *data, gint data_len) { qq_data *qd; guint8 ret = data[0]; - gchar *server_reply, *server_reply_utf8; - gchar *error_msg; + gchar *msg, *msg_utf8; + gchar *error; + PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; - g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR_MISC); + g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR); qd = (qq_data *) gc->proto_data; @@ -415,64 +403,46 @@ purple_debug_info("QQ", "Redirect new server\n"); return process_login_redirect(gc, data, data_len); - case QQ_LOGIN_REPLY_REDIRECT_EX: - purple_debug_error("QQ", "Extend redirect new server, not supported yet\n"); - error_msg = g_strdup( _("Unable login for not support Redirect_EX now") ); - return QQ_LOGIN_REPLY_REDIRECT_EX; - - case QQ_LOGIN_REPLY_ERR_PWD: - server_reply = g_strndup((gchar *)data + 1, data_len - 1); - server_reply_utf8 = qq_to_utf8(server_reply, QQ_CHARSET_DEFAULT); - - purple_debug_error("QQ", "Error password: %s\n", server_reply_utf8); - error_msg = g_strdup_printf( _("Error password: %s"), server_reply_utf8); - - g_free(server_reply); - g_free(server_reply_utf8); - + case 0x0A: /* extend redirect used in QQ2006 */ + error = g_strdup( _("Not support Redirect_EX now") ); + reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; + break; + case 0x05: /* invalid password */ if (!purple_account_get_remember_password(gc->account)) { purple_account_set_password(gc->account, NULL); } - - purple_connection_error_reason(gc, - PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, error_msg); - g_free(error_msg); - - return QQ_LOGIN_REPLY_ERR_PWD; - - case QQ_LOGIN_REPLY_NEED_REACTIVE: - server_reply = g_strndup((gchar *)data + 1, data_len - 1); - server_reply_utf8 = qq_to_utf8(server_reply, QQ_CHARSET_DEFAULT); - - purple_debug_error("QQ", "Need active: %s\n", server_reply_utf8); - error_msg = g_strdup_printf( _("Need active: %s"), server_reply_utf8); - - g_free(server_reply); - g_free(server_reply_utf8); + error = g_strdup( _("Error password")); + reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; + break; + case 0x06: /* need activation */ + error = g_strdup( _("Need active")); + reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; break; default: - purple_debug_error("QQ", - "Unable login for unknow reply code 0x%02X\n", data[0]); - qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ", - data, data_len, - ">>> [default] decrypt and dump"); - error_msg = try_dump_as_gbk(data, data_len); - if (error_msg == NULL) { - error_msg = g_strdup_printf( - _("Unable login for unknow reply code 0x%02X"), data[0] ); - } + qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ", data, data_len, + ">>> [default] decrypt and dump"); + error = g_strdup_printf( + _("Unknow reply code when login (0x%02X)"), + ret ); + reason = PURPLE_CONNECTION_ERROR_OTHER_ERROR; break; } - purple_connection_error_reason(gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_msg); - g_free(error_msg); - return ret; + msg = g_strndup((gchar *)data + 1, data_len - 1); + msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT); + + purple_debug_error("QQ", "%s: %s\n", error, msg_utf8); + purple_connection_error_reason(gc, reason, msg_utf8); + + g_free(error); + g_free(msg); + g_free(msg_utf8); + return QQ_LOGIN_REPLY_ERR; } /* send keep-alive packet to QQ server (it is a heart-beat) */ -void qq_send_packet_keep_alive(PurpleConnection *gc) +void qq_request_keep_alive(PurpleConnection *gc) { qq_data *qd; guint8 raw_data[16] = {0}; @@ -484,11 +454,10 @@ * with this command, server return the same result including * the amount of online QQ users, my ip and port */ bytes += qq_put32(raw_data + bytes, qd->uid); - - qq_send_cmd(gc, QQ_CMD_KEEP_ALIVE, raw_data, 4); + qq_send_cmd(gc, QQ_CMD_KEEP_ALIVE, raw_data, bytes); } -/* parse the return of keep-alive packet, it includes some system information */ +/* parse the return ofqq_process_keep_alive keep-alive packet, it includes some system information */ gboolean qq_process_keep_alive(guint8 *data, gint data_len, PurpleConnection *gc) { qq_data *qd; @@ -505,9 +474,10 @@ return TRUE; /* segments[0] and segment[1] are all 0x30 ("0") */ - qd->total_online = strtol(segments[2], NULL, 10); - if(0 == qd->total_online) { - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + qd->online_total = strtol(segments[2], NULL, 10); + if(0 == qd->online_total) { + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Keep alive error")); } qd->my_ip.s_addr = inet_addr(segments[3]); @@ -519,3 +489,998 @@ g_strfreev(segments); return TRUE; } + +void qq_request_keep_alive_2007(PurpleConnection *gc) +{ + qq_data *qd; + guint8 raw_data[32] = {0}; + gint bytes= 0; + gchar *uid_str; + + qd = (qq_data *) gc->proto_data; + + /* In fact, we can send whatever we like to server + * with this command, server return the same result including + * the amount of online QQ users, my ip and port */ + uid_str = g_strdup_printf("%u", qd->uid); + bytes += qq_putdata(raw_data + bytes, (guint8 *)uid_str, strlen(uid_str)); + qq_send_cmd(gc, QQ_CMD_KEEP_ALIVE, raw_data, bytes); + + g_free(uid_str); +} + +gboolean qq_process_keep_alive_2007(guint8 *data, gint data_len, PurpleConnection *gc) +{ + qq_data *qd; + gint bytes= 0; + guint8 ret; + + g_return_val_if_fail(data != NULL && data_len != 0, FALSE); + + qd = (qq_data *) gc->proto_data; + + /* qq_show_packet("Keep alive reply packet", data, len); */ + + bytes = 0; + bytes += qq_get8(&ret, data + bytes); + bytes += qq_get32(&qd->online_total, data + bytes); + if(0 == qd->online_total) { + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Keep alive error")); + } + + bytes += qq_getIP(&qd->my_ip, data + bytes); + bytes += qq_get16(&qd->my_port, data + bytes); + return TRUE; +} + +void qq_request_keep_alive_2008(PurpleConnection *gc) +{ + qq_data *qd; + guint8 raw_data[16] = {0}; + gint bytes= 0; + + qd = (qq_data *) gc->proto_data; + + /* In fact, we can send whatever we like to server + * with this command, server return the same result including + * the amount of online QQ users, my ip and port */ + bytes += qq_put32(raw_data + bytes, qd->uid); + bytes += qq_putime(raw_data + bytes, &qd->login_time); + qq_send_cmd(gc, QQ_CMD_KEEP_ALIVE, raw_data, bytes); +} + +gboolean qq_process_keep_alive_2008(guint8 *data, gint data_len, PurpleConnection *gc) +{ + qq_data *qd; + gint bytes= 0; + guint8 ret; + time_t server_time; + struct tm *tm_local; + + g_return_val_if_fail(data != NULL && data_len != 0, FALSE); + + qd = (qq_data *) gc->proto_data; + + /* qq_show_packet("Keep alive reply packet", data, len); */ + + bytes = 0; + bytes += qq_get8(&ret, data + bytes); + bytes += qq_get32(&qd->online_total, data + bytes); + if(0 == qd->online_total) { + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Keep alive error")); + } + + bytes += qq_getIP(&qd->my_ip, data + bytes); + bytes += qq_get16(&qd->my_port, data + bytes); + /* skip 2 byytes, 0x(00 3c) */ + bytes += 2; + bytes += qq_getime(&server_time, data + bytes); + /* skip 5 bytes, all are 0 */ + + purple_debug_info("QQ", "keep alive, %s:%d\n", + inet_ntoa(qd->my_ip), qd->my_port); + + tm_local = localtime(&server_time); + purple_debug_info("QQ", "Server time: %d-%d-%d, %d:%d:%d\n", + (1900 +tm_local->tm_year), (1 + tm_local->tm_mon), tm_local->tm_mday, + tm_local->tm_hour, tm_local->tm_min, tm_local->tm_sec); + return TRUE; +} + +/* For QQ2007/2008 */ +void qq_request_get_server(PurpleConnection *gc) +{ + qq_data *qd; + guint8 *buf, *raw_data; + gint bytes; + guint8 *encrypted; + gint encrypted_len; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + + raw_data = g_newa(guint8, 128); + memset(raw_data, 0, 128); + + encrypted = g_newa(guint8, 128 + 16); /* 16 bytes more */ + + bytes = 0; + if (qd->redirect == NULL) { + /* first packet to get server */ + qd->redirect_len = 15; + qd->redirect = g_realloc(qd->redirect, qd->redirect_len); + memset(qd->redirect, 0, qd->redirect_len); + } + bytes += qq_putdata(raw_data + bytes, qd->redirect, qd->redirect_len); + + encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.random_key); + + buf = g_newa(guint8, MAX_PACKET_SIZE); + memset(buf, 0, MAX_PACKET_SIZE); + bytes = 0; + bytes += qq_putdata(buf + bytes, qd->ld.random_key, QQ_KEY_LENGTH); + bytes += qq_putdata(buf + bytes, encrypted, encrypted_len); + + qd->send_seq++; + qq_send_cmd_encrypted(gc, QQ_CMD_GET_SERVER, qd->send_seq, buf, bytes, TRUE); +} + +guint16 qq_process_get_server(PurpleConnection *gc, guint8 *data, gint data_len) +{ + qq_data *qd; + gint bytes; + guint16 ret; + + g_return_val_if_fail (gc != NULL && gc->proto_data != NULL, QQ_LOGIN_REPLY_ERR); + qd = (qq_data *) gc->proto_data; + + g_return_val_if_fail (data != NULL, QQ_LOGIN_REPLY_ERR); + + /* qq_show_packet("Get Server", data, data_len); */ + bytes = 0; + bytes += qq_get16(&ret, data + bytes); + if (ret == 0) { + /* Notice: do not clear redirect_data here. It will be used in login*/ + qd->redirect_ip.s_addr = 0; + return QQ_LOGIN_REPLY_OK; + } + + if (data_len < 15) { + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, + _("Can not decrypt get server reply")); + return QQ_LOGIN_REPLY_ERR; + } + + qd->redirect_len = data_len; + qd->redirect = g_realloc(qd->redirect, qd->redirect_len); + qq_getdata(qd->redirect, qd->redirect_len, data); + /* qq_show_packet("Redirect to", qd->redirect, qd->redirect_len); */ + + qq_getIP(&qd->redirect_ip, data + 11); + purple_debug_info("QQ", "Get server %s\n", inet_ntoa(qd->redirect_ip)); + return QQ_LOGIN_REPLY_REDIRECT; +} + +void qq_request_token_ex(PurpleConnection *gc) +{ + qq_data *qd; + guint8 *buf, *raw_data; + gint bytes; + guint8 *encrypted; + gint encrypted_len; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + + g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0); + + raw_data = g_newa(guint8, MAX_PACKET_SIZE - 16); + memset(raw_data, 0, MAX_PACKET_SIZE - 16); + + encrypted = g_newa(guint8, MAX_PACKET_SIZE); /* 16 bytes more */ + + bytes = 0; + bytes += qq_put8(raw_data + bytes, qd->ld.token_len); + bytes += qq_putdata(raw_data + bytes, qd->ld.token, qd->ld.token_len); + bytes += qq_put8(raw_data + bytes, 3); /* Subcommand */ + bytes += qq_put16(raw_data + bytes, 5); + bytes += qq_put32(raw_data + bytes, 0); + bytes += qq_put8(raw_data + bytes, 0); /* fragment index */ + bytes += qq_put16(raw_data + bytes, 0); /* captcha token */ + + encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.random_key); + + buf = g_newa(guint8, MAX_PACKET_SIZE); + memset(buf, 0, MAX_PACKET_SIZE); + bytes = 0; + bytes += qq_putdata(buf + bytes, qd->ld.random_key, QQ_KEY_LENGTH); + bytes += qq_putdata(buf + bytes, encrypted, encrypted_len); + + qd->send_seq++; + qq_send_cmd_encrypted(gc, QQ_CMD_TOKEN_EX, qd->send_seq, buf, bytes, TRUE); +} + +void qq_request_token_ex_next(PurpleConnection *gc) +{ + qq_data *qd; + guint8 *buf, *raw_data; + gint bytes; + guint8 *encrypted; + gint encrypted_len; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + + g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0); + + raw_data = g_newa(guint8, MAX_PACKET_SIZE - 16); + memset(raw_data, 0, MAX_PACKET_SIZE - 16); + + encrypted = g_newa(guint8, MAX_PACKET_SIZE); /* 16 bytes more */ + + bytes = 0; + bytes += qq_put8(raw_data + bytes, qd->ld.token_len); + bytes += qq_putdata(raw_data + bytes, qd->ld.token, qd->ld.token_len); + bytes += qq_put8(raw_data + bytes, 3); /* Subcommand */ + bytes += qq_put16(raw_data + bytes, 5); + bytes += qq_put32(raw_data + bytes, 0); + bytes += qq_put8(raw_data + bytes, qd->captcha.next_index); /* fragment index */ + bytes += qq_put16(raw_data + bytes, qd->captcha.token_len); /* captcha token */ + bytes += qq_putdata(raw_data + bytes, qd->captcha.token, qd->captcha.token_len); + + encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.random_key); + + buf = g_newa(guint8, MAX_PACKET_SIZE); + memset(buf, 0, MAX_PACKET_SIZE); + bytes = 0; + bytes += qq_putdata(buf + bytes, qd->ld.random_key, QQ_KEY_LENGTH); + bytes += qq_putdata(buf + bytes, encrypted, encrypted_len); + + qd->send_seq++; + qq_send_cmd_encrypted(gc, QQ_CMD_TOKEN_EX, qd->send_seq, buf, bytes, TRUE); + + purple_connection_update_progress(gc, _("Requesting captcha ..."), 3, QQ_CONNECT_STEPS); +} + +static void request_token_ex_code(PurpleConnection *gc, + guint8 *token, guint16 token_len, guint8 *code, guint16 code_len) +{ + qq_data *qd; + guint8 *buf, *raw_data; + gint bytes; + guint8 *encrypted; + gint encrypted_len; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + + g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0); + g_return_if_fail(code != NULL && code_len > 0); + + raw_data = g_newa(guint8, MAX_PACKET_SIZE - 16); + memset(raw_data, 0, MAX_PACKET_SIZE - 16); + + encrypted = g_newa(guint8, MAX_PACKET_SIZE); /* 16 bytes more */ + + bytes = 0; + bytes += qq_put8(raw_data + bytes, qd->ld.token_len); + bytes += qq_putdata(raw_data + bytes, qd->ld.token, qd->ld.token_len); + bytes += qq_put8(raw_data + bytes, 4); /* Subcommand */ + bytes += qq_put16(raw_data + bytes, 5); + bytes += qq_put32(raw_data + bytes, 0); + bytes += qq_put16(raw_data + bytes, code_len); + bytes += qq_putdata(raw_data + bytes, code, code_len); + bytes += qq_put16(raw_data + bytes, qd->ld.token_ex_len); /* login token ex */ + bytes += qq_putdata(raw_data + bytes, qd->ld.token_ex, qd->ld.token_ex_len); + + encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.random_key); + + buf = g_newa(guint8, MAX_PACKET_SIZE); + memset(buf, 0, MAX_PACKET_SIZE); + bytes = 0; + bytes += qq_putdata(buf + bytes, qd->ld.random_key, QQ_KEY_LENGTH); + bytes += qq_putdata(buf + bytes, encrypted, encrypted_len); + + qd->send_seq++; + qq_send_cmd_encrypted(gc, QQ_CMD_TOKEN_EX, qd->send_seq, buf, bytes, TRUE); + + purple_connection_update_progress(gc, _("Checking code of captcha ..."), 3, QQ_CONNECT_STEPS); +} + +typedef struct { + PurpleConnection *gc; + guint8 *token; + guint16 token_len; +} qq_captcha_request; + +static void captcha_request_destory(qq_captcha_request *captcha_req) +{ + g_return_if_fail(captcha_req != NULL); + if (captcha_req->token) g_free(captcha_req->token); + g_free(captcha_req); +} + +static void captcha_input_cancel_cb(qq_captcha_request *captcha_req, + PurpleRequestFields *fields) +{ + captcha_request_destory(captcha_req); + + purple_connection_error_reason(captcha_req->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, + _("Failed captcha verify")); +} + +static void captcha_input_ok_cb(qq_captcha_request *captcha_req, + PurpleRequestFields *fields) +{ + gchar *code; + + g_return_if_fail(captcha_req != NULL && captcha_req->gc != NULL); + + code = utf8_to_qq( + purple_request_fields_get_string(fields, "captcha_code"), + QQ_CHARSET_DEFAULT); + + if (strlen(code) <= 0) { + captcha_input_cancel_cb(captcha_req, fields); + return; + } + + request_token_ex_code(captcha_req->gc, + captcha_req->token, captcha_req->token_len, + (guint8 *)code, strlen(code)); + + captcha_request_destory(captcha_req); +} + +void qq_captcha_input_dialog(PurpleConnection *gc,qq_captcha_data *captcha) +{ + PurpleAccount *account; + PurpleRequestFields *fields; + PurpleRequestFieldGroup *group; + PurpleRequestField *field; + qq_captcha_request *captcha_req; + + g_return_if_fail(captcha->token != NULL && captcha->token_len > 0); + g_return_if_fail(captcha->data != NULL && captcha->data_len > 0); + + captcha_req = g_new0(qq_captcha_request, 1); + captcha_req->gc = gc; + captcha_req->token = g_new0(guint8, captcha->token_len); + g_memmove(captcha_req->token, captcha->token, captcha->token_len); + captcha_req->token_len = captcha->token_len; + + account = purple_connection_get_account(gc); + + fields = purple_request_fields_new(); + group = purple_request_field_group_new(NULL); + purple_request_fields_add_group(fields, group); + + field = purple_request_field_image_new("captcha_img", + _("Captcha Image"), (char *)captcha->data, captcha->data_len); + purple_request_field_group_add_field(group, field); + + field = purple_request_field_string_new("captcha_code", + _("Enter code"), "", FALSE); + purple_request_field_string_set_masked(field, FALSE); + purple_request_field_group_add_field(group, field); + + purple_request_fields(account, + _("QQ Captcha Verifing"), + _("QQ Captcha Verifing"), + _("Please fill code according to image"), + fields, + _("OK"), G_CALLBACK(captcha_input_ok_cb), + _("Cancel"), G_CALLBACK(captcha_input_cancel_cb), + purple_connection_get_account(gc), NULL, NULL, + captcha_req); +} + +guint8 qq_process_token_ex(PurpleConnection *gc, guint8 *data, gint data_len) +{ + qq_data *qd; + int bytes; + guint8 ret; + guint8 sub_cmd; + guint8 reply; + guint16 captcha_len; + guint8 curr_index; + + g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR); + + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, QQ_LOGIN_REPLY_ERR); + qd = (qq_data *) gc->proto_data; + + ret = data[0]; + + bytes = 0; + bytes += qq_get8(&sub_cmd, data + bytes); /* 03: ok; 04: need verifying */ + bytes += 2; /* 0x(00 05) */ + bytes += qq_get8(&reply, data + bytes); + + bytes += qq_get16(&(qd->ld.token_ex_len), data + bytes); + qd->ld.token_ex = g_realloc(qd->ld.token_ex, qd->ld.token_ex_len); + bytes += qq_getdata(qd->ld.token_ex, qd->ld.token_ex_len, data + bytes); + /* qq_show_packet("Get token ex", qd->ld.token_ex, qd->ld.token_ex_len); */ + + if(reply != 1) + { + purple_debug_info("QQ", "Captcha verified, result %d\n", reply); + return QQ_LOGIN_REPLY_OK; + } + + bytes += qq_get16(&captcha_len, data + bytes); + + qd->captcha.data = g_realloc(qd->captcha.data, qd->captcha.data_len + captcha_len); + bytes += qq_getdata(qd->captcha.data + qd->captcha.data_len, captcha_len, data + bytes); + qd->captcha.data_len += captcha_len; + + bytes += qq_get8(&curr_index, data + bytes); + bytes += qq_get8(&qd->captcha.next_index, data + bytes); + + bytes += qq_get16(&qd->captcha.token_len, data + bytes); + qd->captcha.token = g_realloc(qd->captcha.token, qd->captcha.token_len); + bytes += qq_getdata(qd->captcha.token, qd->captcha.token_len, data + bytes); + /* qq_show_packet("Get captcha token", qd->captcha.token, qd->captcha.token_len); */ + + purple_debug_info("QQ", "Request next captcha %d, new %d, total %d\n", + qd->captcha.next_index, captcha_len, qd->captcha.data_len); + if(qd->captcha.next_index > 0) + { + return QQ_LOGIN_REPLY_NEXT_TOKEN_EX; + } + + return QQ_LOGIN_REPLY_CAPTCHA_DLG; +} + +/* source copy from gg's common.c */ +static guint32 crc32_table[256]; +static int crc32_initialized = 0; + +static void crc32_make_table() +{ + guint32 h = 1; + unsigned int i, j; + + memset(crc32_table, 0, sizeof(crc32_table)); + + for (i = 128; i; i >>= 1) { + h = (h >> 1) ^ ((h & 1) ? 0xedb88320L : 0); + + for (j = 0; j < 256; j += 2 * i) + crc32_table[i + j] = crc32_table[j] ^ h; + } + + crc32_initialized = 1; +} + +static guint32 crc32(guint32 crc, const guint8 *buf, int len) +{ + if (!crc32_initialized) + crc32_make_table(); + + if (!buf || len < 0) + return crc; + + crc ^= 0xffffffffL; + + while (len--) + crc = (crc >> 8) ^ crc32_table[(crc ^ *buf++) & 0xff]; + + return crc ^ 0xffffffffL; +} + +void qq_request_check_pwd(PurpleConnection *gc) +{ + qq_data *qd; + guint8 *buf, *raw_data; + gint bytes; + guint8 *encrypted; + gint encrypted_len; + static guint8 header[] = { + 0x00, 0x5F, 0x00, 0x00, 0x08, 0x04, 0x01, 0xE0 + }; + static guint8 unknown[] = { + 0xDB, 0xB9, 0xF3, 0x0B, 0xF9, 0x13, 0x87, 0xB2, + 0xE6, 0x20, 0x43, 0xBE, 0x53, 0xCA, 0x65, 0x03 + }; + + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + + g_return_if_fail(qd->ld.token_ex != NULL && qd->ld.token_ex_len > 0); + + raw_data = g_newa(guint8, MAX_PACKET_SIZE - 16); + memset(raw_data, 0, MAX_PACKET_SIZE - 16); + + encrypted = g_newa(guint8, MAX_PACKET_SIZE); /* 16 bytes more */ + + /* Encrypted password and put in encrypted */ + bytes = 0; + bytes += qq_putdata(raw_data + bytes, qd->ld.pwd_md5, sizeof(qd->ld.pwd_md5)); + bytes += qq_put16(raw_data + bytes, 0); + bytes += qq_put16(raw_data + bytes, rand() & 0xffff); + + encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.pwd_twice_md5); + + /* create packet */ + bytes = 0; + bytes += qq_putdata(raw_data + bytes, header, sizeof(header)); + /* token get from qq_request_token_ex */ + bytes += qq_put8(raw_data + bytes, (guint8)(qd->ld.token_ex_len & 0xff)); + bytes += qq_putdata(raw_data + bytes, qd->ld.token_ex, qd->ld.token_ex_len); + /* password encrypted */ + bytes += qq_put16(raw_data + bytes, encrypted_len); + bytes += qq_putdata(raw_data + bytes, encrypted, encrypted_len); + /* len of unknown + len of CRC32 */ + bytes += qq_put16(raw_data + bytes, sizeof(unknown) + 4); + bytes += qq_putdata(raw_data + bytes, unknown, sizeof(unknown)); + bytes += qq_put32( + raw_data + bytes, crc32(0xFFFFFFFF, unknown, sizeof(unknown))); + + /* put length into first 2 bytes */ + qq_put8(raw_data + 1, bytes - 2); + + /* tail */ + bytes += qq_put16(raw_data + bytes, 0x0003); + bytes += qq_put8(raw_data + bytes, 0); + bytes += qq_put8(raw_data + bytes, qd->ld.pwd_md5[1]); + bytes += qq_put8(raw_data + bytes, qd->ld.pwd_md5[2]); + + /* qq_show_packet("Check password", raw_data, bytes); */ + /* Encrypted by random key*/ + encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.random_key); + + buf = g_newa(guint8, MAX_PACKET_SIZE); + memset(buf, 0, MAX_PACKET_SIZE); + bytes = 0; + bytes += qq_putdata(buf + bytes, qd->ld.random_key, QQ_KEY_LENGTH); + bytes += qq_putdata(buf + bytes, encrypted, encrypted_len); + + qd->send_seq++; + qq_send_cmd_encrypted(gc, QQ_CMD_CHECK_PWD, qd->send_seq, buf, bytes, TRUE); +} + +guint8 qq_process_check_pwd( PurpleConnection *gc, guint8 *data, gint data_len) +{ + qq_data *qd; + int bytes; + guint8 ret; + gchar *error = NULL; + guint16 unknow_token_len; + gchar *msg, *msg_utf8; + guint16 msg_len; + PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; + + g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR); + + g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, QQ_LOGIN_REPLY_ERR); + qd = (qq_data *) gc->proto_data; + + /* qq_show_packet("Check password reply", data, data_len); */ + + bytes = 0; + bytes += qq_get16(&unknow_token_len, data + bytes); /* maybe total length */ + bytes += qq_get8(&ret, data + bytes); + bytes += 4; /* 0x(00 00 6d b9) */ + /* unknow_token_len may 0 when not reply ok*/ + bytes += qq_get16(&unknow_token_len, data + bytes); /* 0x0020 */ + bytes += unknow_token_len; + bytes += qq_get16(&unknow_token_len, data + bytes); /* 0x0020 */ + bytes += unknow_token_len; + + if (ret == 0) { + /* get login_token */ + bytes += qq_get16(&qd->ld.login_token_len, data + bytes); + if (qd->ld.login_token != NULL) g_free(qd->ld.login_token); + qd->ld.login_token = g_new0(guint8, qd->ld.login_token_len); + bytes += qq_getdata(qd->ld.login_token, qd->ld.login_token_len, data + bytes); + /* qq_show_packet("Get login token", qd->ld.login_token, qd->ld.login_token_len); */ + + /* get login_key */ + bytes += qq_getdata(qd->ld.login_key, sizeof(qd->ld.login_key), data + bytes); + /* qq_show_packet("Get login key", qd->ld.login_key, sizeof(qd->ld.login_key)); */ + return QQ_LOGIN_REPLY_OK; + } + + switch (ret) + { + case 0x34: /* invalid password */ + if (!purple_account_get_remember_password(gc->account)) { + purple_account_set_password(gc->account, NULL); + } + error = g_strdup(_("Error password")); + reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; + break; + case 0x33: /* need activation */ + case 0x51: /* need activation */ + error = g_strdup(_("Need active")); + reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; + break; + case 0xBF: /* uid is not exist */ + error = g_strdup(_("invalid user name")); + reason = PURPLE_CONNECTION_ERROR_INVALID_USERNAME; + break; + default: + qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ", data, data_len, + ">>> [default] decrypt and dump"); + error = g_strdup_printf( + _("Unknow reply code when checking password (0x%02X)"), + ret ); + reason = PURPLE_CONNECTION_ERROR_OTHER_ERROR; + break; + } + + bytes += qq_get16(&msg_len, data + bytes); + + msg = g_strndup((gchar *)data + bytes, msg_len); + msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT); + + purple_debug_error("QQ", "%s: %s\n", error, msg_utf8); + purple_connection_error_reason(gc, reason, msg_utf8); + + g_free(error); + g_free(msg); + g_free(msg_utf8); + return QQ_LOGIN_REPLY_ERR; +} + +void qq_request_login_2007(PurpleConnection *gc) +{ + qq_data *qd; + guint8 *buf, *raw_data; + gint bytes; + guint8 *encrypted; + gint encrypted_len; + static const guint8 login_1_16[] = { + 0x56, 0x4E, 0xC8, 0xFB, 0x0A, 0x4F, 0xEF, 0xB3, + 0x7A, 0x5D, 0xD8, 0x86, 0x0F, 0xAC, 0xE5, 0x1A + }; + static const guint8 login_2_16[] = { + 0x5E, 0x22, 0x3A, 0xBE, 0x13, 0xBF, 0xDA, 0x4C, + 0xA9, 0xB7, 0x0B, 0x43, 0x63, 0x51, 0x8E, 0x28 + }; + static const guint8 login_3_83[] = { + 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x01, 0x01, 0x58, 0x83, + 0xD0, 0x00, 0x10, 0x9D, 0x14, 0x64, 0x0A, 0x2E, + 0xE2, 0x11, 0xF7, 0x90, 0xF0, 0xB5, 0x5F, 0x16, + 0xFB, 0x41, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x76, 0x3C, 0xEE, + 0x4A, 0x00, 0x10, 0x86, 0x81, 0xAD, 0x1F, 0xC8, + 0xC9, 0xCC, 0xCF, 0xCA, 0x9F, 0xFF, 0x88, 0xC0, + 0x5C, 0x88, 0xD5 + }; + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + + g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0); + + raw_data = g_newa(guint8, MAX_PACKET_SIZE - 16); + memset(raw_data, 0, MAX_PACKET_SIZE - 16); + + encrypted = g_newa(guint8, MAX_PACKET_SIZE); /* 16 bytes more */ + + /* Encrypted password and put in encrypted */ + bytes = 0; + bytes += qq_putdata(raw_data + bytes, qd->ld.pwd_md5, sizeof(qd->ld.pwd_md5)); + bytes += qq_put16(raw_data + bytes, 0); + bytes += qq_put16(raw_data + bytes, 0xffff); + + encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.pwd_twice_md5); + + /* create packet */ + bytes = 0; + bytes += qq_put16(raw_data + bytes, 0); /* Unknow */ + /* password encrypted */ + bytes += qq_put16(raw_data + bytes, encrypted_len); + bytes += qq_putdata(raw_data + bytes, encrypted, encrypted_len); + /* put data which NULL string encrypted by key pwd_twice_md5 */ + encrypted_len = qq_encrypt(encrypted, (guint8 *) "", 0, qd->ld.pwd_twice_md5); + g_return_if_fail(encrypted_len == 16); + bytes += qq_putdata(raw_data + bytes, encrypted, encrypted_len); + /* unknow fill 0 */ + memset(raw_data + bytes, 0, 19); + bytes += 19; + bytes += qq_putdata(raw_data + bytes, login_1_16, sizeof(login_1_16)); + + bytes += qq_put8(raw_data + bytes, rand() & 0xff); + bytes += qq_put8(raw_data + bytes, qd->login_mode); + /* unknow 10 bytes zero filled*/ + memset(raw_data + bytes, 0, 10); + bytes += 10; + /* redirect data, 15 bytes */ + /* qq_show_packet("Redirect", qd->redirect, qd->redirect_len); */ + bytes += qq_putdata(raw_data + bytes, qd->redirect, qd->redirect_len); + /* unknow fill */ + bytes += qq_putdata(raw_data + bytes, login_2_16, sizeof(login_2_16)); + /* captcha token get from qq_process_token_ex */ + bytes += qq_put8(raw_data + bytes, (guint8)(qd->ld.token_ex_len & 0xff)); + bytes += qq_putdata(raw_data + bytes, qd->ld.token_ex, qd->ld.token_ex_len); + /* unknow fill */ + bytes += qq_putdata(raw_data + bytes, login_3_83, sizeof(login_3_83)); + memset(raw_data + bytes, 0, 332 - sizeof(login_3_83)); + bytes += 332 - sizeof(login_3_83); + + /* qq_show_packet("Login", raw_data, bytes); */ + + encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.login_key); + + buf = g_newa(guint8, MAX_PACKET_SIZE); + memset(buf, 0, MAX_PACKET_SIZE); + bytes = 0; + /* logint token get from qq_process_check_pwd_2007 */ + bytes += qq_put16(buf + bytes, qd->ld.login_token_len); + bytes += qq_putdata(buf + bytes, qd->ld.login_token, qd->ld.login_token_len); + bytes += qq_putdata(buf + bytes, encrypted, encrypted_len); + + qd->send_seq++; + qq_send_cmd_encrypted(gc, QQ_CMD_LOGIN, qd->send_seq, buf, bytes, TRUE); +} + +/* process the login reply packet */ +guint8 qq_process_login_2007( PurpleConnection *gc, guint8 *data, gint data_len) +{ + qq_data *qd; + gint bytes; + guint8 ret; + guint32 uid; + gchar *error; + gchar *msg; + gchar *msg_utf8; + + g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR); + + qd = (qq_data *) gc->proto_data; + + bytes = 0; + bytes += qq_get8(&ret, data + bytes); + if (ret != 0) { + msg = g_strndup((gchar *)data + bytes, data_len - bytes); + msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT); + g_free(msg); + + switch (ret) { + case 0x05: + purple_debug_error("QQ", "Server busy for %s\n", msg_utf8); + return QQ_LOGIN_REPLY_REDIRECT; + case 0x0A: + /* 0a 2d 9a 4b 9a 01 01 00 00 00 05 00 00 00 00 79 0e 5f fd */ + /* Missing get server before login*/ + default: + error = g_strdup_printf( + _("Unknow reply code when login (0x%02X):\n%s"), + ret, msg_utf8); + break; + } + + purple_debug_error("QQ", "%s\n", error); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_OTHER_ERROR, + error); + + qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ", data, data_len, error); + + g_free(error); + g_free(msg_utf8); + return QQ_LOGIN_REPLY_ERR; + } + + bytes += qq_getdata(qd->session_key, sizeof(qd->session_key), data + bytes); + purple_debug_info("QQ", "Got session_key\n"); + get_session_md5(qd->session_md5, qd->uid, qd->session_key); + + bytes += qq_get32(&uid, data + bytes); + if (uid != qd->uid) { + purple_debug_warning("QQ", "My uid in login reply is %d, not %d\n", uid, qd->uid); + } + bytes += qq_getIP(&qd->my_ip, data + bytes); + bytes += qq_get16(&qd->my_port, data + bytes); + bytes += qq_getIP(&qd->my_local_ip, data + bytes); + bytes += qq_get16(&qd->my_local_port, data + bytes); + bytes += qq_getime(&qd->login_time, data + bytes); + /* skip unknow 50 byte */ + bytes += 50; + /* skip client key 32 byte */ + bytes += 32; + /* skip unknow 12 byte */ + bytes += 12; + /* last login */ + bytes += qq_getIP(&qd->last_login_ip, data + bytes); + bytes += qq_getime(&qd->last_login_time[0], data + bytes); + purple_debug_info("QQ", "Last Login: %s, %s\n", + inet_ntoa(qd->last_login_ip), ctime(&qd->last_login_time[0])); + return QQ_LOGIN_REPLY_OK; +} + +void qq_request_login_2008(PurpleConnection *gc) +{ + qq_data *qd; + guint8 *buf, *raw_data; + gint bytes; + guint8 *encrypted; + gint encrypted_len; + guint8 index, count; + + static const guint8 login_1_16[] = { + 0xD2, 0x41, 0x75, 0x12, 0xC2, 0x86, 0x57, 0x10, + 0x78, 0x57, 0xDC, 0x24, 0x8C, 0xAA, 0x8F, 0x4E + }; + + static const guint8 login_2_16[] = { + 0x94, 0x0B, 0x73, 0x7A, 0xA2, 0x51, 0xF0, 0x4B, + 0x95, 0x2F, 0xC6, 0x0A, 0x5B, 0xF6, 0x76, 0x52 + }; + static const guint8 login_3_18[] = { + 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x01, 0x1b, 0x02, 0x84, + 0x50, 0x00 + }; + static const guint8 login_4_16[] = { + 0x2D, 0x49, 0x15, 0x55, 0x78, 0xFC, 0xF3, 0xD4, + 0x53, 0x55, 0x60, 0x9C, 0x37, 0x9F, 0xE9, 0x59 + }; + static const guint8 login_5_6[] = { + 0x02, 0x68, 0xe8, 0x07, 0x83, 0x00 + }; + static const guint8 login_6_16[] = { + 0x3B, 0xCE, 0x43, 0xF1, 0x8B, 0xA4, 0x2B, 0xB5, + 0xB3, 0x51, 0x57, 0xF7, 0x06, 0x4B, 0x18, 0xFC + }; + g_return_if_fail(gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + + g_return_if_fail(qd->ld.token != NULL && qd->ld.token_len > 0); + + raw_data = g_newa(guint8, MAX_PACKET_SIZE - 16); + memset(raw_data, 0, MAX_PACKET_SIZE - 16); + + encrypted = g_newa(guint8, MAX_PACKET_SIZE); /* 16 bytes more */ + + /* Encrypted password and put in encrypted */ + bytes = 0; + bytes += qq_putdata(raw_data + bytes, qd->ld.pwd_md5, sizeof(qd->ld.pwd_md5)); + bytes += qq_put16(raw_data + bytes, 0); + bytes += qq_put16(raw_data + bytes, 0xffff); + + encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.pwd_twice_md5); + + /* create packet */ + bytes = 0; + bytes += qq_put16(raw_data + bytes, 0); /* Unknow */ + /* password encrypted */ + bytes += qq_put16(raw_data + bytes, encrypted_len); + bytes += qq_putdata(raw_data + bytes, encrypted, encrypted_len); + /* put data which NULL string encrypted by key pwd_twice_md5 */ + encrypted_len = qq_encrypt(encrypted, (guint8 *) "", 0, qd->ld.pwd_twice_md5); + g_return_if_fail(encrypted_len == 16); + bytes += qq_putdata(raw_data + bytes, encrypted, encrypted_len); + /* unknow 19 bytes zero filled*/ + memset(raw_data + bytes, 0, 19); + bytes += 19; + bytes += qq_putdata(raw_data + bytes, login_1_16, sizeof(login_1_16)); + + index = rand() % 3; /* can be set as 1 */ + for( count = 0; count < encrypted_len; count++ ) + index ^= encrypted[count]; + for( count = 0; count < sizeof(login_1_16); count++ ) + index ^= login_1_16[count]; + bytes += qq_put8(raw_data + bytes, index); /* random in QQ 2007*/ + + bytes += qq_put8(raw_data + bytes, qd->login_mode); + /* unknow 10 bytes zero filled*/ + memset(raw_data + bytes, 0, 10); + bytes += 10; + /* redirect data, 15 bytes */ + bytes += qq_putdata(raw_data + bytes, qd->redirect, qd->redirect_len); + /* unknow fill */ + bytes += qq_putdata(raw_data + bytes, login_2_16, sizeof(login_2_16)); + /* captcha token get from qq_process_token_ex */ + bytes += qq_put8(raw_data + bytes, (guint8)(qd->ld.token_ex_len & 0xff)); + bytes += qq_putdata(raw_data + bytes, qd->ld.token_ex, qd->ld.token_ex_len); + /* unknow fill */ + bytes += qq_putdata(raw_data + bytes, login_3_18, sizeof(login_3_18)); + bytes += qq_put8(raw_data + bytes , sizeof(login_4_16)); + bytes += qq_putdata(raw_data + bytes, login_4_16, sizeof(login_4_16)); + /* unknow 10 bytes zero filled*/ + memset(raw_data + bytes, 0, 10); + bytes += 10; + /* redirect data, 15 bytes */ + bytes += qq_putdata(raw_data + bytes, qd->redirect, qd->redirect_len); + /* unknow fill */ + bytes += qq_putdata(raw_data + bytes, login_5_6, sizeof(login_5_6)); + bytes += qq_put8(raw_data + bytes , sizeof(login_6_16)); + bytes += qq_putdata(raw_data + bytes, login_6_16, sizeof(login_6_16)); + /* unknow 249 bytes zero filled*/ + memset(raw_data + bytes, 0, 249); + bytes += 249; + + /* qq_show_packet("Login request", raw_data, bytes); */ + encrypted_len = qq_encrypt(encrypted, raw_data, bytes, qd->ld.login_key); + + buf = g_newa(guint8, MAX_PACKET_SIZE); + memset(buf, 0, MAX_PACKET_SIZE); + bytes = 0; + /* logint token get from qq_process_check_pwd_2007 */ + bytes += qq_put16(buf + bytes, qd->ld.login_token_len); + bytes += qq_putdata(buf + bytes, qd->ld.login_token, qd->ld.login_token_len); + bytes += qq_putdata(buf + bytes, encrypted, encrypted_len); + + qd->send_seq++; + qq_send_cmd_encrypted(gc, QQ_CMD_LOGIN, qd->send_seq, buf, bytes, TRUE); +} + +guint8 qq_process_login_2008( PurpleConnection *gc, guint8 *data, gint data_len) +{ + qq_data *qd; + gint bytes; + guint8 ret; + guint32 uid; + gchar *error; + gchar *msg; + gchar *msg_utf8; + + g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR); + + qd = (qq_data *) gc->proto_data; + + bytes = 0; + bytes += qq_get8(&ret, data + bytes); + if (ret != 0) { + msg = g_strndup((gchar *)data + bytes, data_len - bytes); + msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT); + g_free(msg); + + switch (ret) { + case 0x05: + purple_debug_error("QQ", "Server busy for %s\n", msg_utf8); + return QQ_LOGIN_REPLY_REDIRECT; + break; + default: + error = g_strdup_printf( + _("Unknow reply code when login (0x%02X):\n%s"), + ret, msg_utf8); + break; + } + + purple_debug_error("QQ", "%s\n", error); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_OTHER_ERROR, + error); + + qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ", data, data_len, error); + + g_free(error); + g_free(msg_utf8); + return QQ_LOGIN_REPLY_ERR; + } + + bytes += qq_getdata(qd->session_key, sizeof(qd->session_key), data + bytes); + purple_debug_info("QQ", "Got session_key\n"); + get_session_md5(qd->session_md5, qd->uid, qd->session_key); + + bytes += qq_get32(&uid, data + bytes); + if (uid != qd->uid) { + purple_debug_warning("QQ", "My uid in login reply is %d, not %d\n", uid, qd->uid); + } + bytes += qq_getIP(&qd->my_ip, data + bytes); + bytes += qq_get16(&qd->my_port, data + bytes); + bytes += qq_getIP(&qd->my_local_ip, data + bytes); + bytes += qq_get16(&qd->my_local_port, data + bytes); + bytes += qq_getime(&qd->login_time, data + bytes); + /* skip 1 byte, always 0x03 */ + /* skip 1 byte, login mode */ + bytes = 131; + bytes += qq_getIP(&qd->last_login_ip, data + bytes); + bytes += qq_getime(&qd->last_login_time[0], data + bytes); + purple_debug_info("QQ", "Last Login: %s, %s\n", + inet_ntoa(qd->last_login_ip), ctime(&qd->last_login_time[0])); + return QQ_LOGIN_REPLY_OK; +} diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/qq_base.h --- a/libpurple/protocols/qq/qq_base.h Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/qq_base.h Wed Oct 29 21:38:53 2008 +0000 @@ -28,29 +28,51 @@ #include #include "connection.h" -#define QQ_TOKEN_REPLY_OK 0x00 - #define QQ_LOGIN_REPLY_OK 0x00 #define QQ_LOGIN_REPLY_REDIRECT 0x01 -#define QQ_LOGIN_REPLY_ERR_PWD 0x05 -#define QQ_LOGIN_REPLY_NEED_REACTIVE 0x06 -#define QQ_LOGIN_REPLY_REDIRECT_EX 0x0A -#define QQ_LOGIN_REPLY_ERR_MISC 0xff /* defined by myself */ +/* defined by myself */ +#define QQ_LOGIN_REPLY_CAPTCHA_DLG 0xfd +#define QQ_LOGIN_REPLY_NEXT_TOKEN_EX 0xfe +#define QQ_LOGIN_REPLY_ERR 0xff -#define QQ_LOGIN_MODE_NORMAL 0x0a -#define QQ_LOGIN_MODE_AWAY 0x1e -#define QQ_LOGIN_MODE_HIDDEN 0x28 +#define QQ_LOGIN_MODE_NORMAL 0x0a +#define QQ_LOGIN_MODE_AWAY 0x1e +#define QQ_LOGIN_MODE_HIDDEN 0x28 #define QQ_UPDATE_ONLINE_INTERVAL 300 /* in sec */ -void qq_send_packet_token(PurpleConnection *gc); -guint8 qq_process_token_reply(PurpleConnection *gc, guint8 *buf, gint buf_len); +void qq_request_token(PurpleConnection *gc); +guint8 qq_process_token(PurpleConnection *gc, guint8 *buf, gint buf_len); + +void qq_request_login(PurpleConnection *gc); +guint8 qq_process_login( PurpleConnection *gc, guint8 *data, gint data_len); + +void qq_request_logout(PurpleConnection *gc); -void qq_send_packet_login(PurpleConnection *gc); -guint8 qq_process_login_reply( PurpleConnection *gc, guint8 *data, gint data_len); +void qq_request_keep_alive(PurpleConnection *gc); +gboolean qq_process_keep_alive(guint8 *data, gint data_len, PurpleConnection *gc); + +void qq_request_keep_alive_2007(PurpleConnection *gc); +gboolean qq_process_keep_alive_2007(guint8 *data, gint data_len, PurpleConnection *gc); + +void qq_request_keep_alive_2008(PurpleConnection *gc); +gboolean qq_process_keep_alive_2008(guint8 *data, gint data_len, PurpleConnection *gc); -void qq_send_packet_logout(PurpleConnection *gc); +/* for QQ2007/2008 */ +void qq_request_get_server(PurpleConnection *gc); +guint16 qq_process_get_server(PurpleConnection *gc, guint8 *rcved, gint rcved_len); + +void qq_request_token_ex(PurpleConnection *gc); +void qq_request_token_ex_next(PurpleConnection *gc); +guint8 qq_process_token_ex(PurpleConnection *gc, guint8 *buf, gint buf_len); +void qq_captcha_input_dialog(PurpleConnection *gc,qq_captcha_data *captcha); -void qq_send_packet_keep_alive(PurpleConnection *gc); -gboolean qq_process_keep_alive(guint8 *data, gint data_len, PurpleConnection *gc); +void qq_request_check_pwd(PurpleConnection *gc); +guint8 qq_process_check_pwd( PurpleConnection *gc, guint8 *data, gint data_len); + +void qq_request_login_2007(PurpleConnection *gc); +guint8 qq_process_login_2007( PurpleConnection *gc, guint8 *data, gint data_len); + +void qq_request_login_2008(PurpleConnection *gc); +guint8 qq_process_login_2008( PurpleConnection *gc, guint8 *data, gint data_len); #endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/qq_define.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/qq/qq_define.c Wed Oct 29 21:38:53 2008 +0000 @@ -0,0 +1,266 @@ +/** + * @file qq_define.c + * + * purple + * + * Purple 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" + +#include "qq_define.h" + +#define QQ_CLIENT_062E 0x062e /* GB QQ2000c build 0630 */ +#define QQ_CLIENT_072E 0x072e /* EN QQ2000c build 0305 */ +#define QQ_CLIENT_0801 0x0801 /* EN QQ2000c build 0630 */ +#define QQ_CLIENT_0A1D 0x0a1d /* GB QQ2003c build 0808 */ +#define QQ_CLIENT_0B07 0x0b07 /* GB QQ2003c build 0925 */ +#define QQ_CLIENT_0B2F 0x0b2f /* GB QQ2003iii build 0117 */ +#define QQ_CLIENT_0B35 0x0b35 /* GB QQ2003iii build 0304 (offical release) */ +#define QQ_CLIENT_0B37 0x0b37 /* GB QQ2003iii build 0304 (April 05 updates) */ +#define QQ_CLIENT_0E1B 0x0e1b /* QQ2005 ? */ +#define QQ_CLIENT_0E35 0x0e35 /* EN QQ2005 V05.0.200.020 */ +#define QQ_CLIENT_0F15 0x0f15 /* QQ2006 Spring Festival build */ +#define QQ_CLIENT_0F5F 0x0f5f /* QQ2006 final build */ + +#define QQ_CLIENT_0C0B 0x0C0B /* QQ2004 */ +#define QQ_CLIENT_0C0D 0x0C0D /* QQ2004 preview*/ +#define QQ_CLIENT_0C21 0x0C21 /* QQ2004 */ +#define QQ_CLIENT_0C49 0x0C49 /* QQ2004II */ +#define QQ_CLIENT_0D05 0x0D05 /* QQ2005 beta1 */ +#define QQ_CLIENT_0D51 0x0D51 /* QQ2005 beta2 */ +#define QQ_CLIENT_0D61 0x0D61 /* QQ2005 */ +#define QQ_CLIENT_05A5 0x05A5 /* ? */ +#define QQ_CLIENT_05F1 0x0F15 /* QQ2006 Spring Festival */ +#define QQ_CLIENT_0F4B 0x0F4B /* QQ2006 Beta 3 */ + +#define QQ_CLIENT_1105 0x1105 /* QQ2007 beta4*/ +#define QQ_CLIENT_1203 0x1203 /* QQ2008 */ +#define QQ_CLIENT_1205 0x1205 /* QQ2008 Qi Fu */ +#define QQ_CLIENT_120B 0x120B /* QQ2008 July 8.0.978.400 */ +#define QQ_CLIENT_1412 0x1412 /* QQMac 1.0 preview1 build 670 */ +#define QQ_CLIENT_1441 0x1441 /* QQ2009 preview2 */ + +#define QQ_SERVER_0100 0x0100 /* server */ + + +/* given source tag, return its description accordingly */ +const gchar *qq_get_ver_desc(gint source) +{ + switch (source) { + case QQ_CLIENT_062E: + return "GB QQ2000c build 0630"; + case QQ_CLIENT_072E: + return "En QQ2000c build 0305"; + case QQ_CLIENT_0801: + return "En QQ2000c build 0630"; + case QQ_CLIENT_0A1D: + return "GB QQ2003ii build 0808"; + case QQ_CLIENT_0B07: + return "GB QQ2003ii build 0925"; + case QQ_CLIENT_0B2F: + return "GB QQ2003iii build 0117"; + case QQ_CLIENT_0B35: + return "GB QQ2003iii build 0304"; + case QQ_CLIENT_0B37: + return "GB QQ2003iii build 0304 (April 5 update)"; + case QQ_CLIENT_0C0B: + return "QQ2004"; + case QQ_CLIENT_0C0D: + return "QQ2004 preview"; + case QQ_CLIENT_0C21: + return "QQ2004"; + case QQ_CLIENT_0C49: + return "QQ2004II"; + case QQ_CLIENT_0D05: + return "QQ2005 beta1"; + case QQ_CLIENT_0D51: + return "QQ2005 beta2"; + case QQ_CLIENT_0D55: + case QQ_CLIENT_0D61: + return "QQ2005"; + case QQ_CLIENT_0E1B: + return "QQ2005 or QQ2006"; + case QQ_CLIENT_0E35: + return "En QQ2005 V05.0.200.020"; + case QQ_CLIENT_0F15: + return "QQ2006 Spring Festival"; + case QQ_CLIENT_0F4B: + return "QQ2006 beta3"; + case QQ_CLIENT_0F5F: + return "QQ2006 final build"; + case QQ_CLIENT_1105: + return "QQ2007 beta4"; + case QQ_CLIENT_111D: + return "QQ2007"; + case QQ_CLIENT_115B: + case QQ_CLIENT_1203: + case QQ_CLIENT_1205: + case QQ_CLIENT_120B: + return "QQ2008"; + case QQ_CLIENT_1412: + return "QQMac 1.0 preview1 build 670"; + case QQ_CLIENT_1441: + return "QQ2009 preview2"; + case QQ_SERVER_0100: + return "QQ Server 0100"; + default: + return "Unknown Version"; + } +} + +/* given command alias, return the command name accordingly */ +const gchar *qq_get_cmd_desc(gint cmd) +{ + switch (cmd) { + case QQ_CMD_LOGOUT: + return "QQ_CMD_LOGOUT"; + case QQ_CMD_KEEP_ALIVE: + return "QQ_CMD_KEEP_ALIVE"; + case QQ_CMD_UPDATE_INFO: + return "QQ_CMD_UPDATE_INFO"; + case QQ_CMD_SEARCH_USER: + return "QQ_CMD_SEARCH_USER"; + case QQ_CMD_GET_BUDDY_INFO: + return "QQ_CMD_GET_BUDDY_INFO"; + case QQ_CMD_ADD_BUDDY_NO_AUTH: + return "QQ_CMD_ADD_BUDDY_NO_AUTH"; + case QQ_CMD_REMOVE_BUDDY: + return "QQ_CMD_REMOVE_BUDDY"; + case QQ_CMD_ADD_BUDDY_AUTH: + return "QQ_CMD_ADD_BUDDY_AUTH"; + case QQ_CMD_CHANGE_STATUS: + return "QQ_CMD_CHANGE_STATUS"; + case QQ_CMD_ACK_SYS_MSG: + return "QQ_CMD_ACK_SYS_MSG"; + case QQ_CMD_SEND_IM: + return "QQ_CMD_SEND_IM"; + case QQ_CMD_RECV_IM: + return "QQ_CMD_RECV_IM"; + case QQ_CMD_REMOVE_ME: + return "QQ_CMD_REMOVE_ME"; + case QQ_CMD_LOGIN: + return "QQ_CMD_LOGIN"; + case QQ_CMD_GET_BUDDIES_LIST: + return "QQ_CMD_GET_BUDDIES_LIST"; + case QQ_CMD_GET_BUDDIES_ONLINE: + return "QQ_CMD_GET_BUDDIES_ONLINE"; + case QQ_CMD_ROOM: + return "QQ_CMD_ROOM"; + case QQ_CMD_GET_BUDDIES_AND_ROOMS: + return "QQ_CMD_GET_BUDDIES_AND_ROOMS"; + case QQ_CMD_GET_LEVEL: + return "QQ_CMD_GET_LEVEL"; + case QQ_CMD_TOKEN: + return "QQ_CMD_TOKEN"; + case QQ_CMD_RECV_MSG_SYS: + return "QQ_CMD_RECV_MSG_SYS"; + case QQ_CMD_BUDDY_CHANGE_STATUS: + return "QQ_CMD_BUDDY_CHANGE_STATUS"; + case QQ_CMD_GET_SERVER: + return "QQ_CMD_GET_SERVER"; + case QQ_CMD_TOKEN_EX: + return "QQ_CMD_TOKEN_EX"; + case QQ_CMD_CHECK_PWD: + return "QQ_CMD_CHECK_PWD"; + case QQ_CMD_AUTH_CODE: + return "QQ_CMD_AUTH_CODE"; + case QQ_CMD_ADD_BUDDY_NO_AUTH_EX: + return "QQ_CMD_ADD_BUDDY_NO_AUTH_EX"; + case QQ_CMD_ADD_BUDDY_AUTH_EX: + return "QQ_CMD_BUDDY_ADD_AUTH_EX"; + case QQ_CMD_BUDDY_CHECK_CODE: + return "QQ_CMD_BUDDY_CHECK_CODE"; + case QQ_CMD_BUDDY_QUESTION: + return "QQ_CMD_BUDDY_QUESTION"; + default: + return "Unknown CMD"; + } +} + +const gchar *qq_get_room_cmd_desc(gint room_cmd) +{ + switch (room_cmd) { + case QQ_ROOM_CMD_CREATE: + return "QQ_ROOM_CMD_CREATE"; + case QQ_ROOM_CMD_MEMBER_OPT: + return "QQ_ROOM_CMD_MEMBER_OPT"; + case QQ_ROOM_CMD_CHANGE_INFO: + return "QQ_ROOM_CMD_CHANGE_INFO"; + case QQ_ROOM_CMD_GET_INFO: + return "QQ_ROOM_CMD_GET_INFO"; + case QQ_ROOM_CMD_ACTIVATE: + return "QQ_ROOM_CMD_ACTIVATE"; + case QQ_ROOM_CMD_SEARCH: + return "QQ_ROOM_CMD_SEARCH"; + case QQ_ROOM_CMD_JOIN: + return "QQ_ROOM_CMD_JOIN"; + case QQ_ROOM_CMD_AUTH: + return "QQ_ROOM_CMD_AUTH"; + case QQ_ROOM_CMD_QUIT: + return "QQ_ROOM_CMD_QUIT"; + case QQ_ROOM_CMD_SEND_MSG: + return "QQ_ROOM_CMD_SEND_MSG"; + case QQ_ROOM_CMD_GET_ONLINES: + return "QQ_ROOM_CMD_GET_ONLINES"; + case QQ_ROOM_CMD_GET_BUDDIES: + return "QQ_ROOM_CMD_GET_BUDDIES"; + case QQ_ROOM_CMD_CHANGE_CARD: + return "QQ_ROOM_CMD_CHANGE_CARD"; + case QQ_ROOM_CMD_GET_REALNAMES: + return "QQ_ROOM_CMD_GET_REALNAMES"; + case QQ_ROOM_CMD_GET_CARD: + return "QQ_ROOM_CMD_GET_CARD"; + case QQ_ROOM_CMD_SEND_IM_EX: + return "QQ_ROOM_CMD_SEND_IM_EX"; + case QQ_ROOM_CMD_ADMIN: + return "QQ_ROOM_CMD_ADMIN"; + case QQ_ROOM_CMD_TRANSFER: + return "QQ_ROOM_CMD_TRANSFER"; + case QQ_ROOM_CMD_TEMP_CREATE: + return "QQ_ROOM_CMD_TEMP_CREATE"; + case QQ_ROOM_CMD_TEMP_CHANGE_MEMBER: + return "QQ_ROOM_CMD_TEMP_CHANGE_MEMBER"; + case QQ_ROOM_CMD_TEMP_QUIT: + return "QQ_ROOM_CMD_TEMP_QUIT"; + case QQ_ROOM_CMD_TEMP_GET_INFO: + return "QQ_ROOM_CMD_TEMP_GET_INFO"; + case QQ_ROOM_CMD_TEMP_SEND_IM: + return "QQ_ROOM_CMD_TEMP_SEND_IM"; + case QQ_ROOM_CMD_TEMP_GET_MEMBERS: + return "QQ_ROOM_CMD_TEMP_GET_MEMBERS"; + default: + return "Unknown Room Command"; + } +} + +/* check if status means online or offline */ +gboolean is_online(guint8 status) +{ + switch(status) { + case QQ_BUDDY_ONLINE_NORMAL: + case QQ_BUDDY_ONLINE_AWAY: + case QQ_BUDDY_ONLINE_INVISIBLE: + case QQ_BUDDY_ONLINE_BUSY: + return TRUE; + case QQ_BUDDY_CHANGE_TO_OFFLINE: + return FALSE; + } + return FALSE; +} diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/qq_define.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/qq/qq_define.h Wed Oct 29 21:38:53 2008 +0000 @@ -0,0 +1,136 @@ +/** + * @file qq_define.h + * + * purple + * + * Purple 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _QQ_HEADER_INFO_H_ +#define _QQ_HEADER_INFO_H_ + +#include + +#define QQ_UDP_HEADER_LENGTH 7 +#define QQ_TCP_HEADER_LENGTH 9 + +#define QQ_PACKET_TAG 0x02 /* all QQ text packets starts with it */ +#define QQ_PACKET_TAIL 0x03 /* all QQ text packets end with it */ + +#define QQ_CLIENT_0D55 0x0d55 /* QQ2005 used by openq before */ +#define QQ_CLIENT_111D 0x111D /* QQ2007 */ +#define QQ_CLIENT_115B 0x115B /* QQ2008 He Sui*/ + +const gchar *qq_get_ver_desc(gint source); + +/* list of known QQ commands */ +enum { + QQ_CMD_LOGOUT = 0x0001, /* log out */ + QQ_CMD_KEEP_ALIVE = 0x0002, /* get onlines from tencent */ + QQ_CMD_UPDATE_INFO = 0x0004, /* update information */ + QQ_CMD_SEARCH_USER = 0x0005, /* search for user */ + QQ_CMD_GET_BUDDY_INFO = 0x0006, /* get user information */ + QQ_CMD_ADD_BUDDY_NO_AUTH = 0x0009, /* add buddy without auth */ + QQ_CMD_REMOVE_BUDDY = 0x000a, /* delete a buddy */ + QQ_CMD_ADD_BUDDY_AUTH = 0x000b, /* buddy authentication */ + QQ_CMD_CHANGE_STATUS = 0x000d, /* change my online status */ + QQ_CMD_ACK_SYS_MSG = 0x0012, /* ack system message */ + QQ_CMD_SEND_IM = 0x0016, /* send message */ + QQ_CMD_RECV_IM = 0x0017, /* receive message */ + QQ_CMD_REMOVE_ME = 0x001c, /* remove self */ + QQ_CMD_REQUEST_KEY = 0x001d, /* request key for file transfer */ + QQ_CMD_CELL_PHONE_1 = 0x0021, /* cell phone 1 */ + QQ_CMD_LOGIN = 0x0022, /* login */ + QQ_CMD_GET_BUDDIES_LIST = 0x0026, /* get buddies list */ + QQ_CMD_GET_BUDDIES_ONLINE = 0x0027, /* get online buddies list */ + QQ_CMD_CELL_PHONE_2 = 0x0029, /* cell phone 2 */ + QQ_CMD_ROOM = 0x0030, /* room command */ + QQ_CMD_GET_BUDDIES_AND_ROOMS = 0x0058, + QQ_CMD_GET_LEVEL = 0x005C, /* get level for one or more buddies */ + QQ_CMD_TOKEN = 0x0062, /* get login token */ + QQ_CMD_RECV_MSG_SYS = 0x0080, /* receive a system message */ + QQ_CMD_BUDDY_CHANGE_STATUS = 0x0081, /* buddy change status */ + /* for QQ2007*/ + QQ_CMD_GET_SERVER = 0x0091, /* select login server */ + QQ_CMD_TOKEN_EX = 0x00BA, /* get LOGIN token */ + QQ_CMD_CHECK_PWD = 0x00DD, /* Password verify */ + QQ_CMD_AUTH_CODE = 0x00AE, /* the request verification of information */ + QQ_CMD_ADD_BUDDY_NO_AUTH_EX = 0x00A7, /* add friend without auth */ + QQ_CMD_ADD_BUDDY_AUTH_EX = 0x00A8, /* add buddy with auth */ + QQ_CMD_BUDDY_CHECK_CODE = 0x00B5, + QQ_CMD_BUDDY_QUESTION = 0x00B7, +}; + +const gchar *qq_get_cmd_desc(gint type); + +enum { + QQ_ROOM_CMD_CREATE = 0x01, + QQ_ROOM_CMD_MEMBER_OPT = 0x02, + QQ_ROOM_CMD_CHANGE_INFO = 0x03, + QQ_ROOM_CMD_GET_INFO = 0x04, + QQ_ROOM_CMD_ACTIVATE = 0x05, + QQ_ROOM_CMD_SEARCH = 0x06, + QQ_ROOM_CMD_JOIN = 0x07, + QQ_ROOM_CMD_AUTH = 0x08, + QQ_ROOM_CMD_QUIT = 0x09, + QQ_ROOM_CMD_SEND_MSG = 0x0a, + QQ_ROOM_CMD_GET_ONLINES = 0x0b, + QQ_ROOM_CMD_GET_BUDDIES = 0x0c, + + QQ_ROOM_CMD_CHANGE_CARD = 0x0E, + QQ_ROOM_CMD_GET_REALNAMES = 0x0F, + QQ_ROOM_CMD_GET_CARD = 0x10, + QQ_ROOM_CMD_SEND_IM_EX = 0x1A, + QQ_ROOM_CMD_ADMIN = 0x1B, + QQ_ROOM_CMD_TRANSFER = 0x1C, + QQ_ROOM_CMD_TEMP_CREATE = 0x30, + QQ_ROOM_CMD_TEMP_CHANGE_MEMBER = 0x31, + QQ_ROOM_CMD_TEMP_QUIT = 0x32, + QQ_ROOM_CMD_TEMP_GET_INFO = 0x33, + QQ_ROOM_CMD_TEMP_SEND_IM = 0x35, + QQ_ROOM_CMD_TEMP_GET_MEMBERS = 0x37, +}; + +const gchar *qq_get_room_cmd_desc(gint room_cmd); + +enum { + QQ_SERVER_BUDDY_ADDED = 1, + QQ_SERVER_BUDDY_ADD_REQUEST = 2, + QQ_SERVER_BUDDY_ADDED_ME = 3, + QQ_SERVER_BUDDY_REJECTED_ME = 4, + QQ_SERVER_NOTICE= 6, + QQ_SERVER_NEW_CLIENT = 9, + QQ_SERVER_BUDDY_ADDING_EX = 40, + QQ_SERVER_BUDDY_ADD_REQUEST_EX = 41, + QQ_SERVER_BUDDY_ADDED_ANSWER = 42, + QQ_SERVER_BUDDY_ADDED_EX = 43, +}; + +enum { + QQ_BUDDY_OFFLINE = 0x00, + QQ_BUDDY_ONLINE_NORMAL = 10, + QQ_BUDDY_CHANGE_TO_OFFLINE = 20, + QQ_BUDDY_ONLINE_AWAY = 30, + QQ_BUDDY_ONLINE_INVISIBLE = 40, + QQ_BUDDY_ONLINE_BUSY = 50, +}; + +gboolean is_online(guint8 status); + +#endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/qq_network.c --- a/libpurple/protocols/qq/qq_network.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/qq_network.c Wed Oct 29 21:38:53 2008 +0000 @@ -28,9 +28,9 @@ #include "buddy_info.h" #include "group_info.h" -#include "group_free.h" +#include "group_internal.h" #include "qq_crypt.h" -#include "header_info.h" +#include "qq_define.h" #include "qq_base.h" #include "buddy_list.h" #include "packet_parse.h" @@ -160,7 +160,7 @@ qd->connect_watcher = 0; } - if (qd->fd >= 0 && qd->token != NULL && qd->token_len >= 0) { + if (qd->fd >= 0 && qd->ld.token != NULL && qd->ld.token_len > 0) { purple_debug_info("QQ", "Connect ok\n"); return FALSE; } @@ -202,7 +202,8 @@ if (qd->curr_server == NULL || strlen (qd->curr_server) == 0 || qd->connect_retry <= 0) { if ( set_new_server(qd) != TRUE) { - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Failed to connect all servers")); return FALSE; } @@ -220,7 +221,8 @@ qd->connect_retry--; if ( !connect_to_server(gc, server, port) ) { - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect.")); } @@ -228,6 +230,19 @@ return FALSE; /* timeout callback stops */ } +static void redirect_server(PurpleConnection *gc) +{ + qq_data *qd; + qd = (qq_data *) gc->proto_data; + + if (qd->check_watcher > 0) { + purple_timeout_remove(qd->check_watcher); + qd->check_watcher = 0; + } + if (qd->connect_watcher > 0) purple_timeout_remove(qd->connect_watcher); + qd->connect_watcher = purple_timeout_add_seconds(QQ_CONNECT_INTERVAL, qq_connect_later, gc); +} + /* process the incoming packet from qq_pending */ static gboolean packet_process(PurpleConnection *gc, guint8 *buf, gint buf_len) { @@ -242,6 +257,7 @@ guint32 room_id; gint update_class; guint32 ship32; + int ret; qq_transaction *trans; @@ -285,23 +301,22 @@ update_class = qq_trans_get_class(trans); ship32 = qq_trans_get_ship(trans); + if (update_class != 0 || ship32 != 0) { + purple_debug_info("QQ", "Process in Update class %d, ship32 %d\n", + update_class, ship32); + } switch (cmd) { case QQ_CMD_TOKEN: - if (qq_process_token_reply(gc, buf + bytes, bytes_not_read) == QQ_TOKEN_REPLY_OK) { - qq_send_packet_login(gc); - } - break; + case QQ_CMD_GET_SERVER: + case QQ_CMD_TOKEN_EX: + case QQ_CMD_CHECK_PWD: case QQ_CMD_LOGIN: - qq_proc_login_cmd(gc, buf + bytes, bytes_not_read); - /* check is redirect or not, and do it now */ - if (qd->redirect_ip.s_addr != 0) { - if (qd->check_watcher > 0) { - purple_timeout_remove(qd->check_watcher); - qd->check_watcher = 0; + ret = qq_proc_login_cmds(gc, cmd, seq, buf + bytes, bytes_not_read, update_class, ship32); + if (ret != QQ_LOGIN_REPLY_OK) { + if (ret == QQ_LOGIN_REPLY_REDIRECT) { + redirect_server(gc); } - if (qd->connect_watcher > 0) purple_timeout_remove(qd->connect_watcher); - qd->connect_watcher = purple_timeout_add_seconds(QQ_CONNECT_INTERVAL, qq_connect_later, gc); return FALSE; /* do nothing after this function and return now */ } break; @@ -312,10 +327,10 @@ purple_debug_info("QQ", "%s (0x%02X) for room %d, len %d\n", qq_get_room_cmd_desc(room_cmd), room_cmd, room_id, buf_len); #endif - qq_proc_room_cmd(gc, seq, room_cmd, room_id, buf + bytes, bytes_not_read, update_class, ship32); + qq_proc_room_cmds(gc, seq, room_cmd, room_id, buf + bytes, bytes_not_read, update_class, ship32); break; default: - qq_proc_client_cmd(gc, cmd, seq, buf + bytes, bytes_not_read, update_class, ship32); + qq_proc_client_cmds(gc, cmd, seq, buf + bytes, bytes_not_read, update_class, ship32); break; } @@ -342,7 +357,8 @@ qd = (qq_data *) gc->proto_data; if(cond != PURPLE_INPUT_READ) { - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Socket error")); return; } @@ -366,7 +382,9 @@ return; error_msg = g_strdup_printf(_("Lost connection with server:\n%d, %s"), errno, g_strerror(errno)); - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_msg); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + error_msg); g_free(error_msg); return; } else if (buf_len == 0) { @@ -468,7 +486,8 @@ qd = (qq_data *) gc->proto_data; if(cond != PURPLE_INPUT_READ) { - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Socket error")); return; } @@ -478,7 +497,8 @@ /* here we have UDP proxy suppport */ buf_len = read(source, buf, MAX_PACKET_SIZE); if (buf_len <= 0) { - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to read from socket")); return; } @@ -526,7 +546,9 @@ if (ret < 0) { /* TODO: what to do here - do we really have to disconnect? */ purple_debug_error("UDP_SEND_OUT", "Send failed: %d, %s\n", errno, g_strerror(errno)); - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, g_strerror(errno)); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + g_strerror(errno)); } return ret; } @@ -558,8 +580,9 @@ return; else if (ret < 0) { /* TODO: what to do here - do we really have to disconnect? */ - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Write Error")); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Write Error")); return; } @@ -603,7 +626,9 @@ /* TODO: what to do here - do we really have to disconnect? */ purple_debug_error("TCP_SEND_OUT", "Send to socket %d failed: %d, %s\n", qd->fd, errno, g_strerror(errno)); - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, g_strerror(errno)); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + g_strerror(errno)); return ret; } @@ -630,7 +655,8 @@ is_lost_conn = qq_trans_scan(gc); if (is_lost_conn) { purple_connection_error_reason(gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Connection lost")); + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Connection lost")); return TRUE; } @@ -641,7 +667,13 @@ qd->itv_count.keep_alive--; if (qd->itv_count.keep_alive <= 0) { qd->itv_count.keep_alive = qd->itv_config.keep_alive; - qq_send_packet_keep_alive(gc); + if (qd->client_version >= 2008) { + qq_request_keep_alive_2008(gc); + } else if (qd->client_version >= 2007) { + qq_request_keep_alive_2007(gc); + } else { + qq_request_keep_alive(gc); + } return TRUE; } @@ -659,12 +691,15 @@ return TRUE; /* if return FALSE, timeout callback stops */ } -static void do_request_token(PurpleConnection *gc) +static void set_all_keys(PurpleConnection *gc) { qq_data *qd; - gchar *conn_msg; const gchar *passwd; - + guint8 *dest; + int dest_len = QQ_KEY_LENGTH; +#ifndef DEBUG + int bytes; +#endif /* _qq_show_socket("Got login socket", source); */ g_return_if_fail(gc != NULL && gc->proto_data != NULL); @@ -675,27 +710,25 @@ qd->send_seq = rand() & 0xffff; qd->is_login = FALSE; - qd->channel = 1; qd->uid = strtol(purple_account_get_username(purple_connection_get_account(gc)), NULL, 10); +#ifdef DEBUG + memset(qd->ld.random_key, 0x01, sizeof(qd->ld.random_key)); +#else + for (bytes = 0; bytes < sizeof(qd->ld.random_key); bytes++) { + qd->ld.random_key[bytes] = (guint8) (rand() & 0xff); + } +#endif + /* now generate md5 processed passwd */ passwd = purple_account_get_password(purple_connection_get_account(gc)); /* use twice-md5 of user password as session key since QQ 2003iii */ - qq_get_md5(qd->password_twice_md5, sizeof(qd->password_twice_md5), - (guint8 *)passwd, strlen(passwd)); - qq_get_md5(qd->password_twice_md5, sizeof(qd->password_twice_md5), - qd->password_twice_md5, sizeof(qd->password_twice_md5)); + dest = qd->ld.pwd_md5; + qq_get_md5(dest, dest_len, (guint8 *)passwd, strlen(passwd)); - g_return_if_fail(qd->network_watcher == 0); - qd->network_watcher = purple_timeout_add_seconds(qd->itv_config.resend, network_timeout, gc); - - /* Update the login progress status display */ - conn_msg = g_strdup_printf(_("Request token")); - purple_connection_update_progress(gc, conn_msg, 2, QQ_CONNECT_STEPS); - g_free(conn_msg); - - qq_send_packet_token(gc); + dest = qd->ld.pwd_twice_md5; + qq_get_md5(dest, dest_len, qd->ld.pwd_md5, dest_len); } /* the callback function after socket is built @@ -740,7 +773,19 @@ conn->input_handler = purple_input_add(source, PURPLE_INPUT_READ, udp_pending, gc); } - do_request_token( gc ); + g_return_if_fail(qd->network_watcher == 0); + qd->network_watcher = purple_timeout_add_seconds(qd->itv_config.resend, network_timeout, gc); + + set_all_keys( gc ); + + if (qd->client_version >= 2007) { + purple_connection_update_progress(gc, _("Get server ..."), 2, QQ_CONNECT_STEPS); + qq_request_get_server(gc); + return; + } + + purple_connection_update_progress(gc, _("Request token"), 2, QQ_CONNECT_STEPS); + qq_request_token(gc); } #ifndef purple_proxy_connect_udp @@ -811,8 +856,8 @@ if (!hosts || !hosts->data) { purple_connection_error_reason(gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Couldn't resolve host")); + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Couldn't resolve host")); return; } @@ -884,21 +929,19 @@ { PurpleAccount *account ; qq_data *qd; - gchar *conn_msg; g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, FALSE); account = purple_connection_get_account(gc); qd = (qq_data *) gc->proto_data; if (server == NULL || strlen(server) == 0 || port == 0) { - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Invalid server or port")); return FALSE; } - conn_msg = g_strdup_printf( _("Connecting server %s, retries %d"), server, port); - purple_connection_update_progress(gc, conn_msg, 1, QQ_CONNECT_STEPS); - g_free(conn_msg); + purple_connection_update_progress(gc, _("Connecting server ..."), 1, QQ_CONNECT_STEPS); purple_debug_info("QQ", "Connect to %s:%d\n", server, port); @@ -959,7 +1002,7 @@ /* finish all I/O */ if (qd->fd >= 0 && qd->is_login) { - qq_send_packet_logout(gc); + qq_request_logout(gc); } /* not connected */ @@ -984,23 +1027,20 @@ qq_trans_remove_all(gc); - if (qd->token) { - purple_debug_info("QQ", "free token\n"); - g_free(qd->token); - qd->token = NULL; - qd->token_len = 0; - } - memset(qd->inikey, 0, sizeof(qd->inikey)); - memset(qd->password_twice_md5, 0, sizeof(qd->password_twice_md5)); + memset(qd->ld.random_key, 0, sizeof(qd->ld.random_key)); + memset(qd->ld.pwd_md5, 0, sizeof(qd->ld.pwd_md5)); + memset(qd->ld.pwd_twice_md5, 0, sizeof(qd->ld.pwd_twice_md5)); + memset(qd->ld.login_key, 0, sizeof(qd->ld.login_key)); memset(qd->session_key, 0, sizeof(qd->session_key)); memset(qd->session_md5, 0, sizeof(qd->session_md5)); + qd->my_local_ip.s_addr = 0; + qd->my_local_port = 0; qd->my_ip.s_addr = 0; + qd->my_port = 0; - qq_group_free_all(qd); - qq_add_buddy_request_free(qd); - qq_info_query_free(qd); - qq_buddies_list_free(gc->account, qd); + qq_room_data_free_all(gc); + qq_buddy_data_free_all(gc); } static gint packet_encap(qq_data *qd, guint8 *buf, gint maxlen, guint16 cmd, guint16 seq, @@ -1017,7 +1057,7 @@ } /* now comes the normal QQ packet as UDP */ bytes += qq_put8(buf + bytes, QQ_PACKET_TAG); - bytes += qq_put16(buf + bytes, QQ_CLIENT); + bytes += qq_put16(buf + bytes, qd->client_tag); bytes += qq_put16(buf + bytes, cmd); bytes += qq_put16(buf + bytes, seq); @@ -1064,7 +1104,7 @@ } gint qq_send_cmd_encrypted(PurpleConnection *gc, guint16 cmd, guint16 seq, - guint8 *encrypted_data, gint encrypted_len, gboolean is_save2trans) + guint8 *encrypted, gint encrypted_len, gboolean is_save2trans) { gint sent_len; @@ -1074,9 +1114,9 @@ seq, qq_get_cmd_desc(cmd), cmd, encrypted_len); #endif - sent_len = packet_send_out(gc, cmd, seq, encrypted_data, encrypted_len); + sent_len = packet_send_out(gc, cmd, seq, encrypted, encrypted_len); if (is_save2trans) { - qq_trans_add_client_cmd(gc, cmd, seq, encrypted_data, encrypted_len, 0, 0); + qq_trans_add_client_cmd(gc, cmd, seq, encrypted, encrypted_len, 0, 0); } return sent_len; } @@ -1086,7 +1126,7 @@ guint8 *data, gint data_len, gboolean is_save2trans, gint update_class, guint32 ship32) { qq_data *qd; - guint8 *encrypted_data; + guint8 *encrypted; gint encrypted_len; gint bytes_sent; @@ -1095,18 +1135,18 @@ g_return_val_if_fail(data != NULL && data_len > 0, -1); /* at most 16 bytes more */ - encrypted_data = g_newa(guint8, data_len + 16); - encrypted_len = qq_encrypt(encrypted_data, data, data_len, qd->session_key); + encrypted = g_newa(guint8, data_len + 16); + encrypted_len = qq_encrypt(encrypted, data, data_len, qd->session_key); if (encrypted_len < 16) { purple_debug_error("QQ_ENCRYPT", "Error len %d: [%05d] 0x%04X %s\n", encrypted_len, seq, cmd, qq_get_cmd_desc(cmd)); return -1; } - bytes_sent = packet_send_out(gc, cmd, seq, encrypted_data, encrypted_len); + bytes_sent = packet_send_out(gc, cmd, seq, encrypted, encrypted_len); if (is_save2trans) { - qq_trans_add_client_cmd(gc, cmd, seq, encrypted_data, encrypted_len, + qq_trans_add_client_cmd(gc, cmd, seq, encrypted, encrypted_len, update_class, ship32); } return bytes_sent; @@ -1159,7 +1199,7 @@ gint qq_send_server_reply(PurpleConnection *gc, guint16 cmd, guint16 seq, guint8 *data, gint data_len) { qq_data *qd; - guint8 *encrypted_data; + guint8 *encrypted; gint encrypted_len; gint bytes_sent; @@ -1172,16 +1212,16 @@ seq, qq_get_cmd_desc(cmd), cmd, data_len); #endif /* at most 16 bytes more */ - encrypted_data = g_newa(guint8, data_len + 16); - encrypted_len = qq_encrypt(encrypted_data, data, data_len, qd->session_key); + encrypted = g_newa(guint8, data_len + 16); + encrypted_len = qq_encrypt(encrypted, data, data_len, qd->session_key); if (encrypted_len < 16) { purple_debug_error("QQ_ENCRYPT", "Error len %d: [%05d] 0x%04X %s\n", encrypted_len, seq, cmd, qq_get_cmd_desc(cmd)); return -1; } - bytes_sent = packet_send_out(gc, cmd, seq, encrypted_data, encrypted_len); - qq_trans_add_server_reply(gc, cmd, seq, encrypted_data, encrypted_len); + bytes_sent = packet_send_out(gc, cmd, seq, encrypted, encrypted_len); + qq_trans_add_server_reply(gc, cmd, seq, encrypted, encrypted_len); return bytes_sent; } @@ -1192,7 +1232,7 @@ qq_data *qd; guint8 *buf; gint buf_len; - guint8 *encrypted_data; + guint8 *encrypted; gint encrypted_len; gint bytes_sent; guint16 seq; @@ -1217,17 +1257,17 @@ qd->send_seq++; seq = qd->send_seq; - /* Encrypt to encrypted_data with session_key */ + /* Encrypt to encrypted with session_key */ /* at most 16 bytes more */ - encrypted_data = g_newa(guint8, buf_len + 16); - encrypted_len = qq_encrypt(encrypted_data, buf, buf_len, qd->session_key); + encrypted = g_newa(guint8, buf_len + 16); + encrypted_len = qq_encrypt(encrypted, buf, buf_len, qd->session_key); if (encrypted_len < 16) { purple_debug_error("QQ_ENCRYPT", "Error len %d: [%05d] %s (0x%02X)\n", encrypted_len, seq, qq_get_room_cmd_desc(room_cmd), room_cmd); return -1; } - bytes_sent = packet_send_out(gc, QQ_CMD_ROOM, seq, encrypted_data, encrypted_len); + bytes_sent = packet_send_out(gc, QQ_CMD_ROOM, seq, encrypted, encrypted_len); #if 1 /* qq_show_packet("send_room_cmd", buf, buf_len); */ purple_debug_info("QQ", @@ -1235,7 +1275,7 @@ seq, qq_get_room_cmd_desc(room_cmd), room_cmd, room_id, buf_len); #endif - qq_trans_add_room_cmd(gc, seq, room_cmd, room_id, encrypted_data, encrypted_len, + qq_trans_add_room_cmd(gc, seq, room_cmd, room_id, encrypted, encrypted_len, update_class, ship32); return bytes_sent; } @@ -1243,18 +1283,21 @@ gint qq_send_room_cmd_mess(PurpleConnection *gc, guint8 room_cmd, guint32 room_id, guint8 *data, gint data_len, gint update_class, guint32 ship32) { + g_return_val_if_fail(room_cmd > 0, -1); return send_room_cmd(gc, room_cmd, room_id, data, data_len, update_class, ship32); } gint qq_send_room_cmd(PurpleConnection *gc, guint8 room_cmd, guint32 room_id, guint8 *data, gint data_len) { + g_return_val_if_fail(room_cmd > 0 && room_id > 0, -1); return send_room_cmd(gc, room_cmd, room_id, data, data_len, 0, 0); } gint qq_send_room_cmd_noid(PurpleConnection *gc, guint8 room_cmd, guint8 *data, gint data_len) { + g_return_val_if_fail(room_cmd > 0, -1); return send_room_cmd(gc, room_cmd, 0, data, data_len, 0, 0); } diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/qq_network.h --- a/libpurple/protocols/qq/qq_network.h Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/qq_network.h Wed Oct 29 21:38:53 2008 +0000 @@ -30,7 +30,7 @@ #include "qq.h" -#define QQ_CONNECT_STEPS 3 /* steps in connection */ +#define QQ_CONNECT_STEPS 4 /* steps in connection */ gboolean qq_connect_later(gpointer data); void qq_disconnect(PurpleConnection *gc); diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/qq_process.c --- a/libpurple/protocols/qq/qq_process.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/qq_process.c Wed Oct 29 21:38:53 2008 +0000 @@ -30,27 +30,22 @@ #include "buddy_list.h" #include "buddy_opt.h" #include "group_info.h" -#include "group_free.h" #include "char_conv.h" #include "qq_crypt.h" -#include "group_conv.h" -#include "group_find.h" #include "group_internal.h" #include "group_im.h" #include "group_info.h" #include "group_join.h" #include "group_opt.h" -#include "group_search.h" -#include "header_info.h" +#include "qq_define.h" #include "qq_base.h" #include "im.h" #include "qq_process.h" #include "packet_parse.h" #include "qq_network.h" #include "qq_trans.h" -#include "sys_msg.h" #include "utils.h" enum { @@ -60,10 +55,10 @@ }; /* default process, decrypt and dump */ -static void process_cmd_unknow(PurpleConnection *gc,const gchar *title, guint8 *data, gint data_len, guint16 cmd, guint16 seq) +static void process_unknow_cmd(PurpleConnection *gc,const gchar *title, guint8 *data, gint data_len, guint16 cmd, guint16 seq) { qq_data *qd; - gchar *msg_utf8 = NULL; + gchar *msg; g_return_if_fail(data != NULL && data_len != 0); @@ -76,11 +71,390 @@ ">>> [%d] %s -> [default] decrypt and dump", seq, qq_get_cmd_desc(cmd)); - msg_utf8 = try_dump_as_gbk(data, data_len); - if (msg_utf8 != NULL) { - purple_notify_info(gc, _("QQ Error"), title, msg_utf8); - g_free(msg_utf8); + msg = g_strdup_printf("Unknow command 0x%02X, %s", cmd, qq_get_cmd_desc(cmd)); + purple_notify_info(gc, _("QQ Error"), title, msg); + g_free(msg); +} + +/* parse the reply to send_im */ +static void do_im_ack(guint8 *data, gint data_len, PurpleConnection *gc) +{ + qq_data *qd; + + g_return_if_fail(data != NULL && data_len != 0); + + qd = gc->proto_data; + + if (data[0] != 0) { + purple_debug_warning("QQ", "Failed sent IM\n"); + purple_notify_error(gc, _("Error"), _("Failed to send IM."), NULL); + return; + } + + purple_debug_info("QQ", "OK sent IM\n"); +} + +static void do_server_news(guint8 *data, gint data_len, PurpleConnection *gc) +{ + qq_data *qd = (qq_data *) gc->proto_data; + gint bytes; + gchar *title, *brief, *url; + gchar *content; + + g_return_if_fail(data != NULL && data_len != 0); + + /* qq_show_packet("Rcv news", data, data_len); */ + + bytes = 4; /* skip unknown 4 bytes */ + + bytes += qq_get_vstr(&title, QQ_CHARSET_DEFAULT, data + bytes); + bytes += qq_get_vstr(&brief, QQ_CHARSET_DEFAULT, data + bytes); + bytes += qq_get_vstr(&url, QQ_CHARSET_DEFAULT, data + bytes); + + content = g_strdup_printf(_("Server News:\n%s\n%s\n%s"), title, brief, url); + + if (qd->is_show_news) { + qq_got_attention(gc, content); + } else { + purple_debug_info("QQ", "QQ Server news:\n%s\n", content); + } + g_free(title); + g_free(brief); + g_free(url); + g_free(content); +} + +static void do_msg_sys_30(PurpleConnection *gc, guint8 *data, gint data_len) +{ + gint len; + guint8 reply; + gchar **segments, *msg_utf8; + + g_return_if_fail(data != NULL && data_len != 0); + + len = data_len; + + if (NULL == (segments = split_data(data, len, "\x2f", 2))) + return; + + reply = strtol(segments[0], NULL, 10); + if (reply == 1) + purple_debug_warning("QQ", "We are kicked out by QQ server\n"); + + msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT); + qq_got_attention(gc, msg_utf8); +} + +static void do_msg_sys_4c(PurpleConnection *gc, guint8 *data, gint data_len) +{ + gint bytes; + gint msg_len; + GString *content; + gchar *msg = NULL; + + g_return_if_fail(data != NULL && data_len > 0); + + bytes = 6; /* skip 0x(06 00 01 1e 01 1c)*/ + + content = g_string_new(""); + while (bytes < data_len) { + msg_len = qq_get_vstr(&msg, QQ_CHARSET_DEFAULT, data + bytes); + g_string_append(content, msg); + g_string_append(content, "\n"); + g_free(msg); + + if (msg_len <= 1) { + break; + } + bytes += msg_len; + } + if (bytes != data_len) { + purple_debug_warning("QQ", "Failed to read QQ_MSG_SYS_4C\n"); + qq_show_packet("do_msg_sys_4c", data, data_len); + } + qq_got_attention(gc, content->str); + g_string_free(content, FALSE); +} + +static const gchar *get_im_type_desc(gint type) +{ + switch (type) { + case QQ_MSG_TO_BUDDY: + return "QQ_MSG_TO_BUDDY"; + case QQ_MSG_TO_UNKNOWN: + return "QQ_MSG_TO_UNKNOWN"; + case QQ_MSG_UNKNOWN_QUN_IM: + return "QQ_MSG_UNKNOWN_QUN_IM"; + case QQ_MSG_ADD_TO_QUN: + return "QQ_MSG_ADD_TO_QUN"; + case QQ_MSG_DEL_FROM_QUN: + return "QQ_MSG_DEL_FROM_QUN"; + case QQ_MSG_APPLY_ADD_TO_QUN: + return "QQ_MSG_APPLY_ADD_TO_QUN"; + case QQ_MSG_CREATE_QUN: + return "QQ_MSG_CREATE_QUN"; + case QQ_MSG_SYS_30: + return "QQ_MSG_SYS_30"; + case QQ_MSG_SYS_4C: + return "QQ_MSG_SYS_4C"; + case QQ_MSG_APPROVE_APPLY_ADD_TO_QUN: + return "QQ_MSG_APPROVE_APPLY_ADD_TO_QUN"; + case QQ_MSG_REJCT_APPLY_ADD_TO_QUN: + return "QQ_MSG_REJCT_APPLY_ADD_TO_QUN"; + case QQ_MSG_TEMP_QUN_IM: + return "QQ_MSG_TEMP_QUN_IM"; + case QQ_MSG_QUN_IM: + return "QQ_MSG_QUN_IM"; + case QQ_MSG_NEWS: + return "QQ_MSG_NEWS"; + case QQ_MSG_EXTEND: + return "QQ_MSG_EXTEND"; + case QQ_MSG_EXTEND_85: + return "QQ_MSG_EXTEND_85"; + default: + return "QQ_MSG_UNKNOWN"; + } +} + +/* I receive a message, mainly it is text msg, + * but we need to proess other types (group etc) */ +static void process_private_msg(guint8 *data, gint data_len, guint16 seq, PurpleConnection *gc) +{ + qq_data *qd; + gint bytes; + + struct { + guint32 uid_from; + guint32 uid_to; + guint32 seq; + struct in_addr ip_from; + guint16 port_from; + guint16 msg_type; + } header; + + g_return_if_fail(data != NULL && data_len != 0); + + qd = (qq_data *) gc->proto_data; + + if (data_len < 16) { /* we need to ack with the first 16 bytes */ + purple_debug_error("QQ", "MSG is too short\n"); + return; + } else { + /* when we receive a message, + * we send an ACK which is the first 16 bytes of incoming packet */ + qq_send_server_reply(gc, QQ_CMD_RECV_IM, seq, data, 16); + } + + /* check len first */ + if (data_len < 20) { /* length of im_header */ + purple_debug_error("QQ", "Invald MSG header, len %d < 20\n", data_len); + return; } + + bytes = 0; + bytes += qq_get32(&(header.uid_from), data + bytes); + bytes += qq_get32(&(header.uid_to), data + bytes); + bytes += qq_get32(&(header.seq), data + bytes); + /* if the message is delivered via server, it is server IP/port */ + bytes += qq_getIP(&(header.ip_from), data + bytes); + bytes += qq_get16(&(header.port_from), data + bytes); + bytes += qq_get16(&(header.msg_type), data + bytes); + /* im_header prepared */ + + if (header.uid_to != qd->uid) { /* should not happen */ + purple_debug_error("QQ", "MSG to [%d], NOT me\n", header.uid_to); + return; + } + + /* check bytes */ + if (bytes >= data_len - 1) { + purple_debug_warning("QQ", "Empty MSG\n"); + return; + } + + switch (header.msg_type) { + case QQ_MSG_NEWS: + do_server_news(data + bytes, data_len - bytes, gc); + break; + case QQ_MSG_EXTEND: + case QQ_MSG_EXTEND_85: + purple_debug_info("QQ", "MSG from buddy [%d]\n", header.uid_from); + qq_process_extend_im(gc, data + bytes, data_len - bytes); + break; + case QQ_MSG_TO_UNKNOWN: + case QQ_MSG_TO_BUDDY: + purple_debug_info("QQ", "MSG from buddy [%d]\n", header.uid_from); + qq_process_im(gc, data + bytes, data_len - bytes); + break; + case QQ_MSG_UNKNOWN_QUN_IM: + case QQ_MSG_TEMP_QUN_IM: + case QQ_MSG_QUN_IM: + purple_debug_info("QQ", "MSG from room [%d]\n", header.uid_from); + qq_process_room_im(data + bytes, data_len - bytes, header.uid_from, gc, header.msg_type); + break; + case QQ_MSG_ADD_TO_QUN: + purple_debug_info("QQ", "Notice from [%d], Added\n", header.uid_from); + /* uid_from is group id + * we need this to create a dummy group and add to blist */ + qq_process_room_buddy_joined(data + bytes, data_len - bytes, header.uid_from, gc); + break; + case QQ_MSG_DEL_FROM_QUN: + purple_debug_info("QQ", "Notice from room [%d], Removed\n", header.uid_from); + /* uid_from is group id */ + qq_process_room_buddy_removed(data + bytes, data_len - bytes, header.uid_from, gc); + break; + case QQ_MSG_APPLY_ADD_TO_QUN: + purple_debug_info("QQ", "Notice from room [%d], Joined\n", header.uid_from); + /* uid_from is group id */ + qq_process_room_buddy_request_join(data + bytes, data_len - bytes, header.uid_from, gc); + break; + case QQ_MSG_APPROVE_APPLY_ADD_TO_QUN: + purple_debug_info("QQ", "Notice from room [%d], Confirm add in\n", + header.uid_from); + /* uid_from is group id */ + qq_process_room_buddy_approved(data + bytes, data_len - bytes, header.uid_from, gc); + break; + case QQ_MSG_REJCT_APPLY_ADD_TO_QUN: + purple_debug_info("QQ", "Notice from room [%d], Refuse add in\n", + header.uid_from); + /* uid_from is group id */ + qq_process_room_buddy_rejected(data + bytes, data_len - bytes, header.uid_from, gc); + break; + case QQ_MSG_SYS_30: + do_msg_sys_30(gc, data + bytes, data_len - bytes); + break; + case QQ_MSG_SYS_4C: + do_msg_sys_4c(gc, data + bytes, data_len - bytes); + break; + default: + purple_debug_warning("QQ", "MSG from [%d], unknown type %s [0x%04X]\n", + header.uid_from, get_im_type_desc(header.msg_type), header.msg_type); + qq_show_packet("Unknown MSG type", data, data_len); + break; + } +} + +/* Send ACK if the sys message needs an ACK */ +static void request_server_ack(PurpleConnection *gc, gchar *funct_str, gchar *from, guint16 seq) +{ + qq_data *qd; + guint8 *raw_data; + gint bytes; + guint8 bar; + + g_return_if_fail(funct_str != NULL && from != NULL); + qd = (qq_data *) gc->proto_data; + + + bar = 0x1e; + raw_data = g_newa(guint8, strlen(funct_str) + strlen(from) + 16); + + bytes = 0; + bytes += qq_putdata(raw_data + bytes, (guint8 *)funct_str, strlen(funct_str)); + bytes += qq_put8(raw_data + bytes, bar); + bytes += qq_putdata(raw_data + bytes, (guint8 *)from, strlen(from)); + bytes += qq_put8(raw_data + bytes, bar); + bytes += qq_put16(raw_data + bytes, seq); + + qq_send_server_reply(gc, QQ_CMD_ACK_SYS_MSG, 0, raw_data, bytes); +} + +static void do_server_notice(PurpleConnection *gc, gchar *from, gchar *to, + guint8 *data, gint data_len) +{ + qq_data *qd = (qq_data *) gc->proto_data; + gchar *msg, *msg_utf8; + gchar *title, *content; + + g_return_if_fail(from != NULL && to != NULL && data_len > 0); + + msg = g_strndup((gchar *)data, data_len); + msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT); + g_free(msg); + if (msg_utf8 == NULL) { + purple_debug_error("QQ", "Recv NULL sys msg from %s to %s, discard\n", + from, to); + return; + } + + title = g_strdup_printf(_("From %s:"), from); + content = g_strdup_printf(_("Server notice From %s: \n%s"), from, msg_utf8); + + if (qd->is_show_notice) { + qq_got_attention(gc, content); + } else { + purple_debug_info("QQ", "QQ Server notice from %s:\n%s", from, msg_utf8); + } + g_free(msg_utf8); + g_free(title); + g_free(content); +} + +static void process_server_msg(PurpleConnection *gc, guint8 *data, gint data_len, guint16 seq) +{ + qq_data *qd; + guint8 *data_str; + gchar **segments; + gchar *funct_str, *from, *to; + gint bytes, funct; + + g_return_if_fail(data != NULL && data_len != 0); + + qd = (qq_data *) gc->proto_data; + + data_str = g_newa(guint8, data_len + 1); + g_memmove(data_str, data, data_len); + data_str[data_len] = 0x00; + + segments = g_strsplit_set((gchar *) data_str, "\x1f", 0); + g_return_if_fail(segments != NULL); + if (g_strv_length(segments) < 3) { + purple_debug_warning("QQ", "Server message segments is less than 3\n"); + g_strfreev(segments); + return; + } + + bytes = 0; + funct_str = segments[0]; + bytes += strlen(funct_str) + 1; + from = segments[1]; + bytes += strlen(from) + 1; + to = segments[2]; + bytes += strlen(to) + 1; + + request_server_ack(gc, funct_str, from, seq); + + /* qq_show_packet("Server MSG", data, data_len); */ + if (strtol(to, NULL, 10) != qd->uid) { /* not to me */ + purple_debug_error("QQ", "Recv sys msg to [%s], not me!, discard\n", to); + g_strfreev(segments); + return; + } + + funct = strtol(funct_str, NULL, 10); + switch (funct) { + case QQ_SERVER_BUDDY_ADDED: + case QQ_SERVER_BUDDY_ADD_REQUEST: + case QQ_SERVER_BUDDY_ADDED_ME: + case QQ_SERVER_BUDDY_REJECTED_ME: + case QQ_SERVER_BUDDY_ADD_REQUEST_EX: + case QQ_SERVER_BUDDY_ADDING_EX: + case QQ_SERVER_BUDDY_ADDED_ANSWER: + case QQ_SERVER_BUDDY_ADDED_EX: + qq_process_buddy_from_server(gc, funct, from, to, data + bytes, data_len - bytes); + break; + case QQ_SERVER_NOTICE: + do_server_notice(gc, from, to, data + bytes, data_len - bytes); + break; + case QQ_SERVER_NEW_CLIENT: + purple_debug_warning("QQ", "QQ Server has newer client version\n"); + break; + default: + qq_show_packet("Unknown sys msg", data, data_len); + purple_debug_warning("QQ", "Recv unknown sys msg code: %s\n", funct_str); + break; + } + g_strfreev(segments); } void qq_proc_server_cmd(PurpleConnection *gc, guint16 cmd, guint16 seq, guint8 *rcved, gint rcved_len) @@ -113,16 +487,16 @@ /* now process the packet */ switch (cmd) { case QQ_CMD_RECV_IM: - qq_process_recv_im(data, data_len, seq, gc); + process_private_msg(data, data_len, seq, gc); break; case QQ_CMD_RECV_MSG_SYS: - qq_process_msg_sys(data, data_len, seq, gc); + process_server_msg(gc, data, data_len, seq); break; case QQ_CMD_BUDDY_CHANGE_STATUS: qq_process_buddy_change_status(data, data_len, gc); break; default: - process_cmd_unknow(gc, _("Unknow SERVER CMD"), data, data_len, cmd, seq); + process_unknow_cmd(gc, _("Unknow SERVER CMD"), data, data_len, cmd, seq); break; } } @@ -150,36 +524,25 @@ void qq_update_room(PurpleConnection *gc, guint8 room_cmd, guint32 room_id) { qq_data *qd; - qq_group *group; gint ret; g_return_if_fail (gc != NULL && gc->proto_data != NULL); qd = (qq_data *) gc->proto_data; - group = qq_room_search_id(gc, room_id); - if (group == NULL && room_id <= 0) { - purple_debug_info("QQ", "No room, nothing update\n"); - return; - } - if (group == NULL ) { - purple_debug_warning("QQ", "Failed search room id [%d]\n", room_id); - return; - } - switch (room_cmd) { case 0: - qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_INFO, group->id, NULL, 0, + qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_INFO, room_id, NULL, 0, QQ_CMD_CLASS_UPDATE_ROOM, 0); break; case QQ_ROOM_CMD_GET_INFO: - ret = qq_request_room_get_buddies(gc, group, QQ_CMD_CLASS_UPDATE_ROOM); + ret = qq_request_room_get_buddies(gc, room_id, QQ_CMD_CLASS_UPDATE_ROOM); if (ret <= 0) { - qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_ONLINES, group->id, NULL, 0, + qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_ONLINES, room_id, NULL, 0, QQ_CMD_CLASS_UPDATE_ROOM, 0); } break; case QQ_ROOM_CMD_GET_BUDDIES: - qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_ONLINES, group->id, NULL, 0, + qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_ONLINES, room_id, NULL, 0, QQ_CMD_CLASS_UPDATE_ROOM, 0); break; case QQ_ROOM_CMD_GET_ONLINES: @@ -189,43 +552,46 @@ } } -static void update_all_rooms(PurpleConnection *gc, guint8 room_cmd, guint32 room_id) +void qq_update_all_rooms(PurpleConnection *gc, guint8 room_cmd, guint32 room_id) { qq_data *qd; gboolean is_new_turn = FALSE; - qq_group *next_group; + guint32 next_id; g_return_if_fail (gc != NULL && gc->proto_data != NULL); qd = (qq_data *) gc->proto_data; - next_group = qq_room_get_next(gc, room_id); - if (next_group == NULL && room_id <= 0) { - purple_debug_info("QQ", "No room. Finished update\n"); - return; - } - if (next_group == NULL ) { - is_new_turn = TRUE; - next_group = qq_room_get_next(gc, 0); - g_return_if_fail(next_group != NULL); + next_id = qq_room_get_next(gc, room_id); + purple_debug_info("QQ", "Update rooms, next id %d, prev id %d\n", next_id, room_id); + + if (next_id <= 0) { + if (room_id > 0) { + is_new_turn = TRUE; + next_id = qq_room_get_next(gc, 0); + purple_debug_info("QQ", "new turn, id %d\n", next_id); + } else { + purple_debug_info("QQ", "No room. Finished update\n"); + return; + } } switch (room_cmd) { case 0: - qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_INFO, next_group->id, NULL, 0, + qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_INFO, next_id, NULL, 0, QQ_CMD_CLASS_UPDATE_ALL, 0); break; case QQ_ROOM_CMD_GET_INFO: if (!is_new_turn) { - qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_INFO, next_group->id, NULL, 0, + qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_INFO, next_id, NULL, 0, QQ_CMD_CLASS_UPDATE_ALL, 0); } else { - qq_request_room_get_buddies(gc, next_group, QQ_CMD_CLASS_UPDATE_ALL); + qq_request_room_get_buddies(gc, next_id, QQ_CMD_CLASS_UPDATE_ALL); } break; case QQ_ROOM_CMD_GET_BUDDIES: /* last command */ if (!is_new_turn) { - qq_request_room_get_buddies(gc, next_group, QQ_CMD_CLASS_UPDATE_ALL); + qq_request_room_get_buddies(gc, next_id, QQ_CMD_CLASS_UPDATE_ALL); } else { purple_debug_info("QQ", "Finished update\n"); } @@ -244,57 +610,63 @@ switch (cmd) { case 0: - qq_request_buddy_info(gc, qd->uid, QQ_CMD_CLASS_UPDATE_ALL, QQ_BUDDY_INFO_UPDATE_ONLY); + qq_request_buddy_info(gc, qd->uid, QQ_CMD_CLASS_UPDATE_ALL, 0); break; case QQ_CMD_GET_BUDDY_INFO: qq_request_change_status(gc, QQ_CMD_CLASS_UPDATE_ALL); break; case QQ_CMD_CHANGE_STATUS: - qq_request_get_buddies_list(gc, 0, QQ_CMD_CLASS_UPDATE_ALL); + qq_request_get_buddies(gc, 0, QQ_CMD_CLASS_UPDATE_ALL); break; case QQ_CMD_GET_BUDDIES_LIST: qq_request_get_buddies_and_rooms(gc, 0, QQ_CMD_CLASS_UPDATE_ALL); break; case QQ_CMD_GET_BUDDIES_AND_ROOMS: - qq_request_get_buddies_level(gc, QQ_CMD_CLASS_UPDATE_ALL); + if (qd->client_version >= 2007) { + /* QQ2007/2008 can not get buddies level*/ + qq_request_get_buddies_online(gc, 0, QQ_CMD_CLASS_UPDATE_ALL); + } else { + qq_request_get_buddies_level(gc, QQ_CMD_CLASS_UPDATE_ALL); + } break; case QQ_CMD_GET_LEVEL: qq_request_get_buddies_online(gc, 0, QQ_CMD_CLASS_UPDATE_ALL); break; case QQ_CMD_GET_BUDDIES_ONLINE: /* last command */ - update_all_rooms(gc, 0, 0); + qq_update_all_rooms(gc, 0, 0); break; default: break; } + qd->online_last_update = time(NULL); } static void update_all_rooms_online(PurpleConnection *gc, guint8 room_cmd, guint32 room_id) { qq_data *qd; - qq_group *next_group; + guint32 next_id; g_return_if_fail (gc != NULL && gc->proto_data != NULL); qd = (qq_data *) gc->proto_data; - next_group = qq_room_get_next_conv(gc, room_id); - if (next_group == NULL && room_id <= 0) { + next_id = qq_room_get_next_conv(gc, room_id); + if (next_id <= 0 && room_id <= 0) { purple_debug_info("QQ", "No room in conversation, no update online buddies\n"); return; } - if (next_group == NULL ) { + if (next_id <= 0 ) { purple_debug_info("QQ", "finished update rooms' online buddies\n"); return; } switch (room_cmd) { case 0: - qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_ONLINES, next_group->id, NULL, 0, + qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_ONLINES, next_id, NULL, 0, QQ_CMD_CLASS_UPDATE_ALL, 0); break; case QQ_ROOM_CMD_GET_ONLINES: - qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_ONLINES, next_group->id, NULL, 0, + qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_ONLINES, next_id, NULL, 0, QQ_CMD_CLASS_UPDATE_ALL, 0); break; default: @@ -304,6 +676,10 @@ void qq_update_online(PurpleConnection *gc, guint16 cmd) { + qq_data *qd; + g_return_if_fail (gc != NULL && gc->proto_data != NULL); + qd = (qq_data *) gc->proto_data; + switch (cmd) { case 0: qq_request_get_buddies_online(gc, 0, QQ_CMD_CLASS_UPDATE_ONLINE); @@ -315,16 +691,17 @@ default: break; } + qd->online_last_update = time(NULL); } -void qq_proc_room_cmd(PurpleConnection *gc, guint16 seq, +void qq_proc_room_cmds(PurpleConnection *gc, guint16 seq, guint8 room_cmd, guint32 room_id, guint8 *rcved, gint rcved_len, gint update_class, guint32 ship32) { qq_data *qd; guint8 *data; gint data_len; - qq_group *group; + qq_room_data *rmd; gint bytes; guint8 reply_cmd, reply; @@ -355,13 +732,6 @@ return; } - group = qq_room_search_id(gc, room_id); - if (group == NULL) { - purple_debug_warning("QQ", - "Missing room id in [%05d], 0x%02X %s for %d, len %d\n", - seq, room_cmd, qq_get_room_cmd_desc(room_cmd), room_id, rcved_len); - } - bytes = 0; bytes += qq_get8(&reply_cmd, data + bytes); bytes += qq_get8(&reply, data + bytes); @@ -375,17 +745,17 @@ /* now process the packet */ if (reply != QQ_ROOM_CMD_REPLY_OK) { - if (group != NULL) { - qq_set_pending_id(&qd->joining_groups, group->ext_id, FALSE); - } - switch (reply) { /* this should be all errors */ case QQ_ROOM_CMD_REPLY_NOT_MEMBER: - if (group != NULL) { + rmd = qq_room_data_find(gc, room_id); + if (rmd == NULL) { purple_debug_warning("QQ", - _("You are not a member of QQ Qun \"%s\"\n"), group->title_utf8); - group->my_role = QQ_ROOM_ROLE_NO; - qq_group_refresh(gc, group); + "Missing room id in [%05d], 0x%02X %s for %d, len %d\n", + seq, room_cmd, qq_get_room_cmd_desc(room_cmd), room_id, rcved_len); + } else { + purple_debug_warning("QQ", + _("Not a member of room \"%s\"\n"), rmd->title_utf8); + rmd->my_role = QQ_ROOM_ROLE_NO; } break; case QQ_ROOM_CMD_REPLY_SEARCH_ERROR: @@ -402,7 +772,7 @@ /* seems ok so far, so we process the reply according to sub_cmd */ switch (reply_cmd) { case QQ_ROOM_CMD_GET_INFO: - qq_process_room_cmd_get_info(data + bytes, data_len - bytes, gc); + qq_process_room_cmd_get_info(data + bytes, data_len - bytes, ship32, gc); break; case QQ_ROOM_CMD_CREATE: qq_group_process_create_group_reply(data + bytes, data_len - bytes, gc); @@ -417,7 +787,7 @@ qq_group_process_activate_group_reply(data + bytes, data_len - bytes, gc); break; case QQ_ROOM_CMD_SEARCH: - qq_process_group_cmd_search_group(data + bytes, data_len - bytes, gc); + qq_process_room_search(gc, data + bytes, data_len - bytes, ship32); break; case QQ_ROOM_CMD_JOIN: qq_process_group_cmd_join_group(data + bytes, data_len - bytes, gc); @@ -429,19 +799,13 @@ qq_process_group_cmd_exit_group(data + bytes, data_len - bytes, gc); break; case QQ_ROOM_CMD_SEND_MSG: - qq_process_group_cmd_im(data + bytes, data_len - bytes, gc); + qq_process_room_send_im(gc, data + bytes, data_len - bytes); break; case QQ_ROOM_CMD_GET_ONLINES: qq_process_room_cmd_get_onlines(data + bytes, data_len - bytes, gc); - if (group != NULL) - qq_group_conv_refresh_online_member(gc, group); break; case QQ_ROOM_CMD_GET_BUDDIES: qq_process_room_cmd_get_buddies(data + bytes, data_len - bytes, gc); - if (group != NULL) { - group->is_got_info = TRUE; - qq_group_conv_refresh_online_member(gc, group); - } break; default: purple_debug_warning("QQ", "Unknow room cmd 0x%02X %s\n", @@ -451,9 +815,8 @@ if (update_class == QQ_CMD_CLASS_NONE) return; - purple_debug_info("QQ", "Update class %d\n", update_class); if (update_class == QQ_CMD_CLASS_UPDATE_ALL) { - update_all_rooms(gc, room_cmd, room_id); + qq_update_all_rooms(gc, room_cmd, room_id); return; } if (update_class == QQ_CMD_CLASS_UPDATE_ONLINE) { @@ -465,58 +828,159 @@ } } -void qq_proc_login_cmd(PurpleConnection *gc, guint8 *rcved, gint rcved_len) +guint8 qq_proc_login_cmds(PurpleConnection *gc, guint16 cmd, guint16 seq, + guint8 *rcved, gint rcved_len, gint update_class, guint32 ship32) { qq_data *qd; - guint8 *data; - gint data_len; - guint ret_8; + guint8 *data = NULL; + gint data_len = 0; + guint ret_8 = QQ_LOGIN_REPLY_ERR; - g_return_if_fail (gc != NULL && gc->proto_data != NULL); + g_return_val_if_fail (gc != NULL && gc->proto_data != NULL, QQ_LOGIN_REPLY_ERR); qd = (qq_data *) gc->proto_data; + g_return_val_if_fail(rcved_len > 0, QQ_LOGIN_REPLY_ERR); data = g_newa(guint8, rcved_len); - /* May use password_twice_md5 in the past version like QQ2005*/ - data_len = qq_decrypt(data, rcved, rcved_len, qd->inikey); - if (data_len >= 0) { + + switch (cmd) { + case QQ_CMD_TOKEN: + if (qq_process_token(gc, rcved, rcved_len) == QQ_LOGIN_REPLY_OK) { + if (qd->client_version >= 2007) { + qq_request_token_ex(gc); + } else { + qq_request_login(gc); + } + return QQ_LOGIN_REPLY_OK; + } + return QQ_LOGIN_REPLY_ERR; + case QQ_CMD_GET_SERVER: + case QQ_CMD_TOKEN_EX: + data_len = qq_decrypt(data, rcved, rcved_len, qd->ld.random_key); + break; + case QQ_CMD_CHECK_PWD: + /* May use password_twice_md5 in the past version like QQ2005 */ + data_len = qq_decrypt(data, rcved, rcved_len, qd->ld.random_key); + if (data_len >= 0) { + purple_debug_warning("QQ", "Decrypt login packet by random_key, %d bytes\n", data_len); + } else { + data_len = qq_decrypt(data, rcved, rcved_len, qd->ld.pwd_twice_md5); + if (data_len >= 0) { + purple_debug_warning("QQ", "Decrypt login packet by pwd_twice_md5, %d bytes\n", data_len); + } + } + break; + case QQ_CMD_LOGIN: + default: + if (qd->client_version >= 2007) { + data_len = qq_decrypt(data, rcved, rcved_len, qd->ld.pwd_twice_md5); + if (data_len >= 0) { + purple_debug_warning("QQ", "Decrypt login packet by pwd_twice_md5\n"); + } else { + data_len = qq_decrypt(data, rcved, rcved_len, qd->ld.login_key); + if (data_len >= 0) { + purple_debug_warning("QQ", "Decrypt login packet by login_key\n"); + } + } + } else { + /* May use password_twice_md5 in the past version like QQ2005 */ + data_len = qq_decrypt(data, rcved, rcved_len, qd->ld.random_key); + if (data_len >= 0) { + purple_debug_warning("QQ", "Decrypt login packet by random_key\n"); + } else { + data_len = qq_decrypt(data, rcved, rcved_len, qd->ld.pwd_twice_md5); + if (data_len >= 0) { + purple_debug_warning("QQ", "Decrypt login packet by pwd_twice_md5\n"); + } + } + } + break; + } + + if (data_len < 0) { purple_debug_warning("QQ", - "Decrypt login reply packet with inikey, %d bytes\n", data_len); - } else { - data_len = qq_decrypt(data, rcved, rcved_len, qd->password_twice_md5); - if (data_len >= 0) { - purple_debug_warning("QQ", - "Decrypt login reply packet with password_twice_md5, %d bytes\n", data_len); - } else { - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + "Can not decrypt login cmd, [%05d], 0x%04X %s, len %d\n", + seq, cmd, qq_get_cmd_desc(cmd), rcved_len); + qq_show_packet("Can not decrypted", rcved, rcved_len); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, _("Can not decrypt login reply")); - return; - } + return QQ_LOGIN_REPLY_ERR; } - ret_8 = qq_process_login_reply(gc, data, data_len); - if (ret_8 != QQ_LOGIN_REPLY_OK) { - return; - } - - purple_debug_info("QQ", "Login repliess OK; everything is fine\n"); - - purple_connection_set_state(gc, PURPLE_CONNECTED); - qd->is_login = TRUE; /* must be defined after sev_finish_login */ + switch (cmd) { + case QQ_CMD_GET_SERVER: + ret_8 = qq_process_get_server(gc, data, data_len); + if ( ret_8 == QQ_LOGIN_REPLY_OK) { + qq_request_token(gc); + } else if ( ret_8 == QQ_LOGIN_REPLY_REDIRECT) { + return QQ_LOGIN_REPLY_REDIRECT; + } + break; + case QQ_CMD_TOKEN_EX: + ret_8 = qq_process_token_ex(gc, data, data_len); + if (ret_8 == QQ_LOGIN_REPLY_OK) { + qq_request_check_pwd(gc); + } else if (ret_8 == QQ_LOGIN_REPLY_NEXT_TOKEN_EX) { + qq_request_token_ex_next(gc); + } else if (ret_8 == QQ_LOGIN_REPLY_CAPTCHA_DLG) { + qq_captcha_input_dialog(gc, &(qd->captcha)); + g_free(qd->captcha.token); + g_free(qd->captcha.data); + memset(&qd->captcha, 0, sizeof(qd->captcha)); + } + break; + case QQ_CMD_CHECK_PWD: + ret_8 = qq_process_check_pwd(gc, data, data_len); + if (ret_8 != QQ_LOGIN_REPLY_OK) { + return ret_8; + } + if (qd->client_version == 2008) { + qq_request_login_2008(gc); + } else { + qq_request_login_2007(gc); + } + break; + case QQ_CMD_LOGIN: + if (qd->client_version == 2008) { + ret_8 = qq_process_login_2008(gc, data, data_len); + if ( ret_8 == QQ_LOGIN_REPLY_REDIRECT) { + qq_request_get_server(gc); + return QQ_LOGIN_REPLY_OK; + } + } else if (qd->client_version == 2007) { + ret_8 = qq_process_login_2007(gc, data, data_len); + if ( ret_8 == QQ_LOGIN_REPLY_REDIRECT) { + qq_request_get_server(gc); + return QQ_LOGIN_REPLY_OK; + } + } else { + ret_8 = qq_process_login(gc, data, data_len); + } + if (ret_8 != QQ_LOGIN_REPLY_OK) { + return ret_8; + } - /* now initiate QQ Qun, do it first as it may take longer to finish */ - qq_group_init(gc); + purple_connection_update_progress(gc, _("Logined"), QQ_CONNECT_STEPS - 1, QQ_CONNECT_STEPS); + purple_debug_info("QQ", "Login repliess OK; everything is fine\n"); + purple_connection_set_state(gc, PURPLE_CONNECTED); + qd->is_login = TRUE; /* must be defined after sev_finish_login */ - /* Now goes on updating my icon/nickname, not showing info_window */ - qd->modifying_face = FALSE; + /* now initiate QQ Qun, do it first as it may take longer to finish */ + qq_room_data_initial(gc); - /* is_login, but we have packets before login */ - qq_trans_process_remained(gc); + /* is_login, but we have packets before login */ + qq_trans_process_remained(gc); - qq_update_all(gc, 0); - return; + qq_update_all(gc, 0); + break; + default: + process_unknow_cmd(gc, _("Unknow LOGIN CMD"), data, data_len, cmd, seq); + return QQ_LOGIN_REPLY_ERR; + } + return QQ_LOGIN_REPLY_OK; } -void qq_proc_client_cmd(PurpleConnection *gc, guint16 cmd, guint16 seq, +void qq_proc_client_cmds(PurpleConnection *gc, guint16 cmd, guint16 seq, guint8 *rcved, gint rcved_len, gint update_class, guint32 ship32) { qq_data *qd; @@ -553,50 +1017,56 @@ switch (cmd) { case QQ_CMD_UPDATE_INFO: - qq_process_modify_info_reply(data, data_len, gc); + qq_process_change_info(gc, data, data_len); break; - case QQ_CMD_ADD_BUDDY_WO_AUTH: - qq_process_add_buddy_reply(data, data_len, seq, gc); + case QQ_CMD_ADD_BUDDY_NO_AUTH: + qq_process_add_buddy_no_auth(gc, data, data_len, ship32); break; - case QQ_CMD_DEL_BUDDY: - qq_process_remove_buddy_reply(data, data_len, gc); + case QQ_CMD_REMOVE_BUDDY: + qq_process_remove_buddy(gc, data, data_len, ship32); break; - case QQ_CMD_REMOVE_SELF: - qq_process_remove_self_reply(data, data_len, gc); + case QQ_CMD_REMOVE_ME: + qq_process_buddy_remove_me(gc, data, data_len, ship32); break; - case QQ_CMD_BUDDY_AUTH: - qq_process_add_buddy_auth_reply(data, data_len, gc); + case QQ_CMD_ADD_BUDDY_AUTH: + qq_process_add_buddy_auth(data, data_len, gc); break; case QQ_CMD_GET_BUDDY_INFO: - qq_process_get_buddy_info(data, data_len, gc); + qq_process_get_buddy_info(data, data_len, ship32, gc); break; case QQ_CMD_CHANGE_STATUS: - qq_process_change_status_reply(data, data_len, gc); + qq_process_change_status(data, data_len, gc); break; case QQ_CMD_SEND_IM: - qq_process_send_im_reply(data, data_len, gc); + do_im_ack(data, data_len, gc); break; case QQ_CMD_KEEP_ALIVE: - qq_process_keep_alive(data, data_len, gc); + if (qd->client_version >= 2008) { + qq_process_keep_alive_2008(data, data_len, gc); + } else if (qd->client_version >= 2007) { + qq_process_keep_alive_2007(data, data_len, gc); + } else { + qq_process_keep_alive(data, data_len, gc); + } break; case QQ_CMD_GET_BUDDIES_ONLINE: - ret_8 = qq_process_get_buddies_online_reply(data, data_len, gc); + ret_8 = qq_process_get_buddies_online(data, data_len, gc); if (ret_8 > 0 && ret_8 < 0xff) { purple_debug_info("QQ", "Requesting for more online buddies\n"); qq_request_get_buddies_online(gc, ret_8, update_class); return; } purple_debug_info("QQ", "All online buddies received\n"); - qq_refresh_all_buddy_status(gc); + qq_update_buddyies_status(gc); break; case QQ_CMD_GET_LEVEL: qq_process_get_level_reply(data, data_len, gc); break; case QQ_CMD_GET_BUDDIES_LIST: - ret_16 = qq_process_get_buddies_list_reply(data, data_len, gc); + ret_16 = qq_process_get_buddies(data, data_len, gc); if (ret_16 > 0 && ret_16 < 0xffff) { purple_debug_info("QQ", "Requesting for more buddies\n"); - qq_request_get_buddies_list(gc, ret_16, update_class); + qq_request_get_buddies(gc, ret_16, update_class); return; } purple_debug_info("QQ", "All buddies received. Requesting buddies' levels\n"); @@ -610,8 +1080,23 @@ } purple_debug_info("QQ", "All buddies and groups received\n"); break; + case QQ_CMD_AUTH_CODE: + qq_process_auth_code(gc, data, data_len, ship32); + break; + case QQ_CMD_BUDDY_QUESTION: + qq_process_question(gc, data, data_len, ship32); + break; + case QQ_CMD_ADD_BUDDY_NO_AUTH_EX: + qq_process_add_buddy_no_auth_ex(gc, data, data_len, ship32); + break; + case QQ_CMD_ADD_BUDDY_AUTH_EX: + qq_process_add_buddy_auth_ex(gc, data, data_len, ship32); + break; + case QQ_CMD_BUDDY_CHECK_CODE: + qq_process_buddy_check_code(gc, data, data_len); + break; default: - process_cmd_unknow(gc, _("Unknow reply CMD"), data, data_len, cmd, seq); + process_unknow_cmd(gc, _("Unknow CLIENT CMD"), data, data_len, cmd, seq); is_unknow = TRUE; break; } diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/qq_process.h --- a/libpurple/protocols/qq/qq_process.h Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/qq_process.h Wed Oct 29 21:38:53 2008 +0000 @@ -34,13 +34,15 @@ QQ_CMD_CLASS_NONE = 0, QQ_CMD_CLASS_UPDATE_ALL, QQ_CMD_CLASS_UPDATE_ONLINE, + QQ_CMD_CLASS_UPDATE_BUDDY, QQ_CMD_CLASS_UPDATE_ROOM, }; -void qq_proc_login_cmd(PurpleConnection *gc, guint8 *rcved, gint rcved_len); -void qq_proc_client_cmd(PurpleConnection *gc, guint16 cmd, guint16 seq, +guint8 qq_proc_login_cmds(PurpleConnection *gc, guint16 cmd, guint16 seq, guint8 *rcved, gint rcved_len, gint update_class, guint32 ship32); -void qq_proc_room_cmd(PurpleConnection *gc, guint16 seq, +void qq_proc_client_cmds(PurpleConnection *gc, guint16 cmd, guint16 seq, + guint8 *rcved, gint rcved_len, gint update_class, guint32 ship32); +void qq_proc_room_cmds(PurpleConnection *gc, guint16 seq, guint8 room_cmd, guint32 room_id, guint8 *rcved, gint rcved_len, gint update_class, guint32 ship32); @@ -49,5 +51,6 @@ void qq_update_all(PurpleConnection *gc, guint16 cmd); void qq_update_online(PurpleConnection *gc, guint16 cmd); void qq_update_room(PurpleConnection *gc, guint8 room_cmd, guint32 room_id); +void qq_update_all_rooms(PurpleConnection *gc, guint8 room_cmd, guint32 room_id); #endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/qq_trans.c --- a/libpurple/protocols/qq/qq_trans.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/qq_trans.c Wed Oct 29 21:38:53 2008 +0000 @@ -30,7 +30,7 @@ #include "prefs.h" #include "request.h" -#include "header_info.h" +#include "qq_define.h" #include "qq_network.h" #include "qq_process.h" #include "qq_trans.h" @@ -131,6 +131,7 @@ } trans->update_class = update_class; + trans->ship32 = ship32; return trans; } diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/send_file.c --- a/libpurple/protocols/qq/send_file.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/send_file.c Wed Oct 29 21:38:53 2008 +0000 @@ -31,7 +31,7 @@ #include "buddy_list.h" #include "file_trans.h" -#include "header_info.h" +#include "qq_define.h" #include "im.h" #include "qq_base.h" #include "packet_parse.h" @@ -54,8 +54,8 @@ static int _qq_in_same_lan(ft_info *info) { if (info->remote_internet_ip == info->local_internet_ip) return 1; - purple_debug_info("QQ", - "Not in the same LAN, remote internet ip[%x], local internet ip[%x]\n", + purple_debug_info("QQ", + "Not in the same LAN, remote internet ip[%x], local internet ip[%x]\n", info->remote_internet_ip , info->local_internet_ip); return 0; @@ -87,7 +87,7 @@ info = (ft_info *) xfer->data; sinlen = sizeof(sin); r = recvfrom(info->recv_fd, buf, len, 0, (struct sockaddr *) &sin, &sinlen); - purple_debug_info("QQ", + purple_debug_info("QQ", "==> recv %d bytes from File UDP Channel, remote ip[%s], remote port[%d]\n", r, inet_ntoa(sin.sin_addr), g_ntohs(sin.sin_port)); return r; @@ -121,11 +121,8 @@ sin.sin_port = g_htons(info->remote_minor_port); sin.sin_addr.s_addr = g_htonl(info->remote_real_ip); } - purple_debug_info("QQ", "sending to channel: %d.%d.%d.%d:%d\n", - (int)sin.sin_addr.s_addr & 0xff, - (int)(sin.sin_addr.s_addr >> 8) & 0xff, - (int)(sin.sin_addr.s_addr >> 16) & 0xff, - (int)sin.sin_addr.s_addr >> 24, + purple_debug_info("QQ", "sending to channel: %s:%d\n", + inet_ntoa(sin.sin_addr), (int)g_ntohs(sin.sin_port) ); return sendto(info->sender_fd, buf, len, 0, (struct sockaddr *) &sin, sizeof(sin)); @@ -301,7 +298,7 @@ /* 004-007: sender uid */ bytes += qq_put32 (raw_data + bytes, to_uid); /* 008-009: sender client version */ - bytes += qq_put16 (raw_data + bytes, QQ_CLIENT); + bytes += qq_put16 (raw_data + bytes, qd->client_tag); /* 010-013: receiver uid */ bytes += qq_put32 (raw_data + bytes, qd->uid); /* 014-017: sender uid */ @@ -380,7 +377,7 @@ static void _qq_xfer_init_socket(PurpleXfer *xfer) { - gint sockfd, listen_port = 0, i; + gint sockfd, listen_port = 0, i; socklen_t sin_len; struct sockaddr_in sin; ft_info *info; @@ -389,7 +386,7 @@ g_return_if_fail(xfer->data != NULL); info = (ft_info *) xfer->data; - /* debug + /* debug info->local_real_ip = 0x7f000001; */ info->local_real_ip = g_ntohl(inet_addr(purple_network_get_my_ip(-1))); @@ -460,7 +457,7 @@ raw_data = g_newa(guint8, packet_len); bytes = 0; - bytes += _qq_create_packet_file_header(raw_data + bytes, to_uid, + bytes += _qq_create_packet_file_header(raw_data + bytes, to_uid, QQ_FILE_TRANS_REQ, qd, FALSE); bytes += qq_fill_conn_info(raw_data + bytes, info); /* 079: 0x20 */ @@ -682,7 +679,7 @@ } /* process reject im for file transfer request */ -void qq_process_recv_file_reject (guint8 *data, gint data_len, +void qq_process_recv_file_reject (guint8 *data, gint data_len, guint32 sender_uid, PurpleConnection *gc) { gchar *msg, *filename; @@ -711,7 +708,7 @@ } /* process cancel im for file transfer request */ -void qq_process_recv_file_cancel (guint8 *data, gint data_len, +void qq_process_recv_file_cancel (guint8 *data, gint data_len, guint32 sender_uid, PurpleConnection *gc) { gchar *msg, *filename; @@ -774,7 +771,7 @@ gchar *sender_name, **fileinfo; ft_info *info; PurpleBuddy *b; - qq_buddy *q_bud; + qq_buddy_data *bd; gint bytes; g_return_if_fail (data != NULL && data_len != 0); @@ -785,7 +782,7 @@ info->local_internet_port = qd->my_port; info->local_real_ip = 0x00000000; info->to_uid = sender_uid; - + if (data_len <= 2 + 30 + QQ_CONN_INFO_LEN) { purple_debug_warning("QQ", "Received file request message is empty\n"); return; @@ -804,36 +801,37 @@ /* FACE from IP detector, ignored by gfhuang */ if(g_ascii_strcasecmp(fileinfo[0], "FACE") == 0) { purple_debug_warning("QQ", - "Received a FACE ip detect from qq-%d, so he/she must be online :)\n", sender_uid); + "Received a FACE ip detect from %d, so he/she must be online :)\n", sender_uid); b = purple_find_buddy(gc->account, sender_name); - q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data; - if (q_bud) { + bd = (b == NULL) ? NULL : (qq_buddy_data *) b->proto_data; + if (bd) { if(0 != info->remote_real_ip) { - g_memmove(&(q_bud->ip), &info->remote_real_ip, sizeof(q_bud->ip)); - q_bud->port = info->remote_minor_port; + g_memmove(&(bd->ip), &info->remote_real_ip, sizeof(bd->ip)); + bd->port = info->remote_minor_port; } else if (0 != info->remote_internet_ip) { - g_memmove(&(q_bud->ip), &info->remote_internet_ip, sizeof(q_bud->ip)); - q_bud->port = info->remote_major_port; + g_memmove(&(bd->ip), &info->remote_internet_ip, sizeof(bd->ip)); + bd->port = info->remote_major_port; } - if(!is_online(q_bud->status)) { - q_bud->status = QQ_BUDDY_ONLINE_INVISIBLE; - qq_update_buddy_contact(gc, q_bud); + if(!is_online(bd->status)) { + bd->status = QQ_BUDDY_ONLINE_INVISIBLE; + bd->last_update = time(NULL); + qq_update_buddy_status(gc, bd->uid, bd->status, bd->comm_flag); } - else + else purple_debug_info("QQ", "buddy %d is already online\n", sender_uid); } - else + else purple_debug_warning("QQ", "buddy %d is not in list\n", sender_uid); - g_free(sender_name); + g_free(sender_name); g_strfreev(fileinfo); return; } - + xfer = purple_xfer_new(purple_connection_get_account(gc), PURPLE_XFER_RECEIVE, sender_name); @@ -875,7 +873,7 @@ */ } -void qq_process_recv_file_notify(guint8 *data, gint data_len, +void qq_process_recv_file_notify(guint8 *data, gint data_len, guint32 sender_uid, PurpleConnection *gc) { gint bytes; @@ -892,7 +890,7 @@ purple_debug_warning("QQ", "Received file notify message is empty\n"); return; } - + bytes = 0; bytes += qq_get16(&(info->send_seq), data + bytes); diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/sys_msg.c --- a/libpurple/protocols/qq/sys_msg.c Mon Oct 27 23:13:11 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,357 +0,0 @@ -/** - * @file sys_msg.c - * - * purple - * - * Purple 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ - -#include "debug.h" -#include "internal.h" -#include "notify.h" -#include "request.h" - -#include "buddy_info.h" -#include "buddy_list.h" -#include "buddy_opt.h" -#include "char_conv.h" -#include "header_info.h" -#include "packet_parse.h" -#include "qq.h" -#include "qq_network.h" -#include "sys_msg.h" -#include "utils.h" - -enum { - QQ_MSG_SYS_BEING_ADDED = 0x01, - QQ_MSG_SYS_ADD_CONTACT_REQUEST = 0x02, - QQ_MSG_SYS_ADD_CONTACT_APPROVED = 0x03, - QQ_MSG_SYS_ADD_CONTACT_REJECTED = 0x04, - QQ_MSG_SYS_NOTICE= 0x06, - QQ_MSG_SYS_NEW_VERSION = 0x09 -}; - -/* Henry: private function for reading/writing of system log */ -static void _qq_sys_msg_log_write(PurpleConnection *gc, gchar *msg, gchar *from) -{ - PurpleLog *log; - PurpleAccount *account; - - account = purple_connection_get_account(gc); - - log = purple_log_new(PURPLE_LOG_IM, - "systemim", - account, - NULL, - time(NULL), - NULL - ); - purple_log_write(log, PURPLE_MESSAGE_SYSTEM, from, - time(NULL), msg); - purple_log_free(log); -} - -/* suggested by rakescar@linuxsir, can still approve after search */ -static void _qq_search_before_auth_with_gc_and_uid(gc_and_uid *g) -{ - PurpleConnection *gc; - guint32 uid; - gchar *nombre; - - g_return_if_fail(g != NULL); - - gc = g->gc; - uid = g->uid; - g_return_if_fail(gc != 0 && uid != 0); - - qq_send_packet_get_info(gc, uid, TRUE); /* we want to see window */ - - nombre = uid_to_purple_name(uid); - purple_request_action - (gc, NULL, _("Do you approve the requestion?"), "", - PURPLE_DEFAULT_ACTION_NONE, - purple_connection_get_account(gc), nombre, NULL, - g, 2, - _("Reject"), G_CALLBACK(qq_reject_add_request_with_gc_and_uid), - _("Approve"), G_CALLBACK(qq_approve_add_request_with_gc_and_uid)); - g_free(nombre); -} - -static void _qq_search_before_add_with_gc_and_uid(gc_and_uid *g) -{ - PurpleConnection *gc; - guint32 uid; - gchar *nombre; - - g_return_if_fail(g != NULL); - - gc = g->gc; - uid = g->uid; - g_return_if_fail(gc != 0 && uid != 0); - - qq_send_packet_get_info(gc, uid, TRUE); /* we want to see window */ - nombre = uid_to_purple_name(uid); - purple_request_action - (gc, NULL, _("Do you add the buddy?"), "", - PURPLE_DEFAULT_ACTION_NONE, - purple_connection_get_account(gc), nombre, NULL, - g, 2, - _("Cancel"), NULL, - _("Add"), G_CALLBACK(qq_add_buddy_with_gc_and_uid)); - g_free(nombre); -} - -/* Send ACK if the sys message needs an ACK */ -static void _qq_send_packet_ack_msg_sys(PurpleConnection *gc, guint8 code, guint32 from, guint16 seq) -{ - qq_data *qd; - guint8 bar, *ack; - gchar *str; - gint ack_len, bytes; - - qd = (qq_data *) gc->proto_data; - - str = g_strdup_printf("%d", from); - bar = 0x1e; - ack_len = 1 + 1 + strlen(str) + 1 + 2; - ack = g_newa(guint8, ack_len); - - bytes = 0; - bytes += qq_put8(ack + bytes, code); - bytes += qq_put8(ack + bytes, bar); - bytes += qq_putdata(ack + bytes, (guint8 *) str, strlen(str)); - bytes += qq_put8(ack + bytes, bar); - bytes += qq_put16(ack + bytes, seq); - - g_free(str); - - if (bytes == ack_len) /* creation OK */ - qq_send_server_reply(gc, QQ_CMD_ACK_SYS_MSG, 0, ack, ack_len); - else - purple_debug_error("QQ", - "Fail creating sys msg ACK, expect %d bytes, build %d bytes\n", ack_len, bytes); -} - -/* when you are added by a person, QQ server will send sys message */ -static void _qq_process_msg_sys_being_added(PurpleConnection *gc, gchar *from, gchar *to, gchar *msg_utf8) -{ - gchar *message; - PurpleBuddy *b; - guint32 uid; - gc_and_uid *g; - gchar *name; - - g_return_if_fail(from != NULL && to != NULL); - - uid = strtol(from, NULL, 10); - name = uid_to_purple_name(uid); - b = purple_find_buddy(gc->account, name); - - if (b == NULL) { /* the person is not in my list */ - g = g_new0(gc_and_uid, 1); - g->gc = gc; - g->uid = uid; /* only need to get value */ - message = g_strdup_printf(_("You have been added by %s"), from); - _qq_sys_msg_log_write(gc, message, from); - purple_request_action(gc, NULL, message, - _("Would you like to add him?"), - PURPLE_DEFAULT_ACTION_NONE, - purple_connection_get_account(gc), name, NULL, - g, 3, - _("Cancel"), NULL, - _("Add"), G_CALLBACK(qq_add_buddy_with_gc_and_uid), - _("Search"), G_CALLBACK(_qq_search_before_add_with_gc_and_uid)); - } else { - message = g_strdup_printf(_("%s added you [%s] to buddy list"), from, to); - _qq_sys_msg_log_write(gc, message, from); - purple_notify_info(gc, _("QQ Budy"), _("Successed:"), message); - } - - g_free(name); - g_free(message); -} - -/* you are rejected by the person */ -static void _qq_process_msg_sys_add_contact_rejected(PurpleConnection *gc, gchar *from, gchar *to, gchar *msg_utf8) -{ - gchar *message, *reason; - - g_return_if_fail(from != NULL && to != NULL); - - message = g_strdup_printf(_("Requestion rejected by %s"), from); - reason = g_strdup_printf(_("Message: %s"), msg_utf8); - _qq_sys_msg_log_write(gc, message, from); - - purple_notify_info(gc, _("QQ Buddy"), message, reason); - g_free(message); - g_free(reason); -} - -/* the buddy approves your request of adding him/her as your friend */ -static void _qq_process_msg_sys_add_contact_approved(PurpleConnection *gc, gchar *from, gchar *to, gchar *msg_utf8) -{ - gchar *message; - qq_data *qd; - - g_return_if_fail(from != NULL && to != NULL); - - qd = (qq_data *) gc->proto_data; - qq_add_buddy_by_recv_packet(gc, strtol(from, NULL, 10), TRUE, TRUE); - - message = g_strdup_printf(_("Requestion approved by %s"), from); - _qq_sys_msg_log_write(gc, message, from); - purple_notify_info(gc, _("QQ Buddy"), _("Notice:"), message); - - g_free(message); -} - -/* someone wants to add you to his buddy list */ -static void _qq_process_msg_sys_add_contact_request(PurpleConnection *gc, gchar *from, gchar *to, gchar *msg_utf8) -{ - gchar *message, *reason; - guint32 uid; - gc_and_uid *g, *g2; - PurpleBuddy *b; - gchar *name; - - g_return_if_fail(from != NULL && to != NULL); - - uid = strtol(from, NULL, 10); - g = g_new0(gc_and_uid, 1); - g->gc = gc; - g->uid = uid; - - name = uid_to_purple_name(uid); - - /* TODO: this should go through purple_account_request_authorization() */ - message = g_strdup_printf(_("%s wants to add you [%s] as a friend"), from, to); - reason = g_strdup_printf(_("Message: %s"), msg_utf8); - _qq_sys_msg_log_write(gc, message, from); - - purple_request_action - (gc, NULL, message, reason, PURPLE_DEFAULT_ACTION_NONE, - purple_connection_get_account(gc), name, NULL, - g, 3, - _("Reject"), - G_CALLBACK(qq_reject_add_request_with_gc_and_uid), - _("Approve"), - G_CALLBACK(qq_approve_add_request_with_gc_and_uid), - _("Search"), G_CALLBACK(_qq_search_before_auth_with_gc_and_uid)); - - g_free(message); - g_free(reason); - - /* XXX: Is this needed once the above goes through purple_account_request_authorization()? */ - b = purple_find_buddy(gc->account, name); - if (b == NULL) { /* the person is not in my list */ - g2 = g_new0(gc_and_uid, 1); - g2->gc = gc; - g2->uid = strtol(from, NULL, 10); - message = g_strdup_printf(_("%s is not in buddy list"), from); - purple_request_action(gc, NULL, message, - _("Would you add?"), PURPLE_DEFAULT_ACTION_NONE, - purple_connection_get_account(gc), name, NULL, - g2, 3, - _("Cancel"), NULL, - _("Add"), G_CALLBACK(qq_add_buddy_with_gc_and_uid), - _("Search"), G_CALLBACK(_qq_search_before_add_with_gc_and_uid)); - g_free(message); - } - - g_free(name); -} - -static void _qq_process_msg_sys_notice(PurpleConnection *gc, gchar *from, gchar *to, gchar *msg_utf8) -{ - qq_data *qd = (qq_data *) gc->proto_data; - gchar *title, *content; - - g_return_if_fail(from != NULL && to != NULL); - - title = g_strdup_printf(_("From %s:"), from); - content = g_strdup_printf(_("%s"), msg_utf8); - - if (qd->is_show_notice) { - purple_notify_info(gc, _("QQ Server Notice"), title, content); - } else { - purple_debug_info("QQ", "QQ Server notice from %s:\n%s", from, msg_utf8); -} - g_free(title); - g_free(content); -} - -void qq_process_msg_sys(guint8 *data, gint data_len, guint16 seq, PurpleConnection *gc) -{ - qq_data *qd; - gchar **segments, *code, *from, *to, *msg, *msg_utf8; - - g_return_if_fail(data != NULL && data_len != 0); - - qd = (qq_data *) gc->proto_data; - - if (NULL == (segments = split_data(data, data_len, "\x1f", 4))) - return; - code = segments[0]; - from = segments[1]; - to = segments[2]; - msg = segments[3]; - - _qq_send_packet_ack_msg_sys(gc, code[0], strtol(from, NULL, 10), seq); - - if (strtol(to, NULL, 10) != qd->uid) { /* not to me */ - purple_debug_error("QQ", "Recv sys msg to [%s], not me!, discard\n", to); - g_strfreev(segments); - return; - } - - msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT); - if (from == NULL && msg_utf8) { - purple_debug_error("QQ", "Recv NULL sys msg to [%s], discard\n", to); - g_strfreev(segments); - g_free(msg_utf8); - return; - } - - switch (strtol(code, NULL, 10)) { - case QQ_MSG_SYS_BEING_ADDED: - _qq_process_msg_sys_being_added(gc, from, to, msg_utf8); - break; - case QQ_MSG_SYS_ADD_CONTACT_REQUEST: - _qq_process_msg_sys_add_contact_request(gc, from, to, msg_utf8); - break; - case QQ_MSG_SYS_ADD_CONTACT_APPROVED: - _qq_process_msg_sys_add_contact_approved(gc, from, to, msg_utf8); - break; - case QQ_MSG_SYS_ADD_CONTACT_REJECTED: - _qq_process_msg_sys_add_contact_rejected(gc, from, to, msg_utf8); - break; - case QQ_MSG_SYS_NOTICE: - _qq_process_msg_sys_notice(gc, from, to, msg_utf8); - break; - case QQ_MSG_SYS_NEW_VERSION: - purple_debug_warning("QQ", - "QQ server says there is newer version than %s\n", qq_get_ver_desc(QQ_CLIENT)); - break; - default: - purple_debug_warning("QQ", "Recv unknown sys msg code: %s\n", code); - purple_debug_warning("QQ", "the msg is : %s\n", msg_utf8); - } - g_free(msg_utf8); - g_strfreev(segments); -} diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/sys_msg.h --- a/libpurple/protocols/qq/sys_msg.h Mon Oct 27 23:13:11 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -/** - * @file sys_msg.h - * - * purple - * - * Purple 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ - -#ifndef _QQ_SYS_MSG_H_ -#define _QQ_SYS_MSG_H_ - -#include -#include "connection.h" - -void qq_process_msg_sys(guint8 *data, gint data_len, guint16 seq, PurpleConnection *gc); - -#endif diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/utils.c --- a/libpurple/protocols/qq/utils.c Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/utils.c Wed Oct 29 21:38:53 2008 +0000 @@ -91,12 +91,6 @@ return g_strdup_printf("%d", index); } -gint qq_string_to_dec_value(const gchar *str) -{ - g_return_val_if_fail(str != NULL, 0); - return strtol(str, NULL, 10); -} - /* split the given data(len) with delimit, * check the number of field matches the expected_fields (<=0 means all) * return gchar* array (needs to be freed by g_strfreev later), or NULL */ @@ -104,7 +98,7 @@ { guint8 *input; gchar **segments; - gint i, j; + gint count, j; g_return_val_if_fail(data != NULL && len != 0 && delimit != 0, NULL); @@ -118,22 +112,17 @@ if (expected_fields <= 0) return segments; - for (i = 0; segments[i] != NULL; i++) {; - } - if (i < expected_fields) { /* not enough fields */ - purple_debug_error("QQ", "Invalid data, expect %d fields, found only %d, discard\n", - expected_fields, i); - g_strfreev(segments); + count = g_strv_length(segments); + if (count < expected_fields) { /* not enough fields */ + purple_debug_error("QQ", "Less fields %d then %d\n", count, expected_fields); return NULL; - } else if (i > expected_fields) { /* more fields, OK */ - purple_debug_warning("QQ", "Dangerous data, expect %d fields, found %d, return all\n", - expected_fields, i); + } else if (count > expected_fields) { /* more fields, OK */ + purple_debug_warning("QQ", "More fields %d than %d\n", count, expected_fields); /* free up those not used */ - for (j = expected_fields; j < i; j++) { + for (j = expected_fields; j < count; j++) { purple_debug_warning("QQ", "field[%d] is %s\n", j, segments[j]); g_free(segments[j]); } - segments[expected_fields] = NULL; } @@ -183,20 +172,6 @@ return g_strdup_printf(QQ_NAME_FORMAT, uid); } -/* convert name displayed in a chat channel to original QQ UID */ -gchar *chat_name_to_purple_name(const gchar *const name) -{ - const gchar *tmp; - gchar *ret; - - g_return_val_if_fail(name != NULL, NULL); - - tmp = (gchar *) purple_strcasestr(name, "(qq-"); - ret = g_strndup(tmp + 4, strlen(name) - (tmp - name) - 4 - 1); - - return ret; -} - /* try to dump the data as GBK */ gchar* try_dump_as_gbk(const guint8 *const data, gint len) { @@ -335,7 +310,7 @@ } void qq_hex_dump(PurpleDebugLevel level, const char *category, - const guint8 *pdata, gint bytes, + const guint8 *pdata, gint bytes, const char *format, ...) { va_list args; @@ -361,25 +336,6 @@ void qq_show_packet(const gchar *desc, const guint8 *buf, gint len) { - qq_hex_dump(PURPLE_DEBUG_INFO, "QQ", buf, len, desc); + qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ", buf, len, desc); } -/* convert face num from packet (0-299) to local face (1-100) */ -gchar *face_to_icon_str(gint face) -{ - gchar *icon_num_str; - gint icon_num = face / 3 + 1; - icon_num_str = g_strdup_printf("%d", icon_num); - return icon_num_str; -} - -/* return the location of the buddy icon dir - * any application using libpurple but not installing the QQ buddy icons - * under datadir needs to set the pref below, or buddy icons won't work */ -const char *qq_buddy_icon_dir(void) -{ - if (purple_prefs_exists("/prpl/qq/buddy_icon_dir")) - return purple_prefs_get_string("/prpl/qq/buddy_icon_dir"); - else - return NULL; -} diff -r a4dfc631b140 -r e17605cc2c0a libpurple/protocols/qq/utils.h --- a/libpurple/protocols/qq/utils.h Mon Oct 27 23:13:11 2008 +0000 +++ b/libpurple/protocols/qq/utils.h Wed Oct 29 21:38:53 2008 +0000 @@ -34,7 +34,6 @@ gchar *get_name_by_index_str(gchar **array, const gchar *index_str, gint amount); gchar *get_index_str_by_name(gchar **array, const gchar *name, gint amount); -gint qq_string_to_dec_value(const gchar *str); gchar **split_data(guint8 *data, gint len, const gchar *delimit, gint expected_fields); @@ -43,18 +42,13 @@ guint32 purple_name_to_uid(const gchar *name); gchar *uid_to_purple_name(guint32 uid); -gchar *chat_name_to_purple_name(const gchar *const name); - -gchar *face_to_icon_str(gint face); gchar *try_dump_as_gbk(const guint8 *const data, gint len); void qq_show_packet(const gchar *desc, const guint8 *buf, gint len); void qq_hex_dump(PurpleDebugLevel level, const char *category, - const guint8 *pdata, gint bytes, + const guint8 *pdata, gint bytes, const char *format, ...); guint8 *hex_str_to_bytes(const gchar *buf, gint *out_len); -const gchar *qq_buddy_icon_dir(void); - #endif diff -r a4dfc631b140 -r e17605cc2c0a pidgin/gtkdialogs.c --- a/pidgin/gtkdialogs.c Mon Oct 27 23:13:11 2008 +0000 +++ b/pidgin/gtkdialogs.c Wed Oct 29 21:38:53 2008 +0000 @@ -70,7 +70,7 @@ /* Order: Alphabetical by Last Name */ static const struct developer developers[] = { {"Daniel 'datallah' Atallah", NULL, NULL}, - {"John 'rekkanoryo' Bailey", N_("bug master"), NULL}, + {"John 'rekkanoryo' Bailey", N_("bug master"), "rekkanoryo@pidgin.im"}, {"Ethan 'Paco-Paco' Blanton", NULL, NULL}, {"Hylke Bons", N_("artist"), "h.bons@student.rug.nl"}, {"Thomas Butter", NULL, NULL}, @@ -80,8 +80,8 @@ {"Mark 'KingAnt' Doliner", NULL, "mark@kingant.net"}, {"Sean Egan", NULL, "sean.egan@gmail.com"}, {"Casey Harkins", NULL, NULL}, - {"Gary 'grim' Kramlich", NULL, NULL}, - {"Richard 'rlaager' Laager", NULL, NULL}, + {"Gary 'grim' Kramlich", NULL, "grim@pidgin.im"}, + {"Richard 'rlaager' Laager", NULL, "rlaager@pidgin.im"}, {"Richard 'wabz' Nelson", NULL, NULL}, {"Christopher 'siege' O'Brien", NULL, "taliesein@users.sf.net"}, {"Bartosz Oler", NULL, NULL}, @@ -134,7 +134,7 @@ }; /* Order: Code, then Alphabetical by Last Name */ -static const struct translator current_translators[] = { +static const struct translator translators[] = { {N_("Afrikaans"), "af", "Samuel Murray", "afrikaans@gmail.com"}, {N_("Afrikaans"), "af", "Friedel Wolff", "friedel@translate.org.za"}, {N_("Arabic"), "ar", "Khaled Hosny", "khaledhosny@eglug.org"}, @@ -286,6 +286,45 @@ {NULL, NULL, NULL, NULL} }; +static void +add_developers(GString *str, const struct developer *list) +{ + for (; list->name != NULL; list++) { + if (list->email != NULL) { + g_string_append_printf(str, " %s%s%s%s
", + list->email, _(list->name), + list->role ? " (" : "", + list->role ? _(list->role) : "", + list->role ? ")" : ""); + } else { + g_string_append_printf(str, " %s%s%s%s
", + _(list->name), + list->role ? " (" : "", + list->role ? _(list->role) : "", + list->role ? ")" : ""); + } + } +} + +static void +add_translators(GString *str, const struct translator *list) +{ + for (; list->language != NULL; list++) { + if (list->email != NULL) { + g_string_append_printf(str, " %s (%s) - %s
", + _(list->language), + list->abbr, + list->email, + _(list->name)); + } else { + g_string_append_printf(str, " %s (%s) - %s
", + _(list->language), + list->abbr, + _(list->name)); + } + } +} + void pidgin_dialogs_destroy_all() { @@ -351,7 +390,6 @@ GtkWidget *button; GtkTextIter iter; GString *str; - int i; AtkObject *obj; char* filename, *tmp; GdkPixbuf *pixbuf; @@ -422,119 +460,37 @@ /* Current Developers */ g_string_append_printf(str, "%s:
", _("Current Developers")); - for (i = 0; developers[i].name != NULL; i++) { - if (developers[i].email != NULL) { - g_string_append_printf(str, " %s%s%s%s
", - developers[i].email, _(developers[i].name), - developers[i].role ? " (" : "", - developers[i].role ? _(developers[i].role) : "", - developers[i].role ? ")" : ""); - } else { - g_string_append_printf(str, " %s%s%s%s
", - _(developers[i].name), - developers[i].role ? " (" : "", - developers[i].role ? _(developers[i].role) : "", - developers[i].role ? ")" : ""); - } - } + add_developers(str, developers); g_string_append(str, "
"); /* Crazy Patch Writers */ g_string_append_printf(str, "%s:
", _("Crazy Patch Writers")); - for (i = 0; patch_writers[i].name != NULL; i++) { - if (patch_writers[i].email != NULL) { - g_string_append_printf(str, " %s%s%s%s
", - patch_writers[i].email, _(patch_writers[i].name), - patch_writers[i].role ? " (" : "", - patch_writers[i].role ? _(patch_writers[i].role) : "", - patch_writers[i].role ? ")" : ""); - } else { - g_string_append_printf(str, " %s%s%s%s
", - _(patch_writers[i].name), - patch_writers[i].role ? " (" : "", - patch_writers[i].role ? _(patch_writers[i].role) : "", - patch_writers[i].role ? ")" : ""); - } - } + add_developers(str, patch_writers); g_string_append(str, "
"); /* Retired Developers */ g_string_append_printf(str, "%s:
", _("Retired Developers")); - for (i = 0; retired_developers[i].name != NULL; i++) { - if (retired_developers[i].email != NULL) { - g_string_append_printf(str, " %s%s%s%s
", - retired_developers[i].email, _(retired_developers[i].name), - retired_developers[i].role ? " (" : "", - retired_developers[i].role ? _(retired_developers[i].role) : "", - retired_developers[i].role ? ")" : ""); - } else { - g_string_append_printf(str, " %s%s%s%s
", - _(retired_developers[i].name), - retired_developers[i].role ? " (" : "", - retired_developers[i].role ? _(retired_developers[i].role) : "", - retired_developers[i].role ? ")" : ""); - } - } + add_developers(str, retired_developers); g_string_append(str, "
"); /* Retired Crazy Patch Writers */ g_string_append_printf(str, "%s:
", _("Retired Crazy Patch Writers")); - for (i = 0; retired_patch_writers[i].name != NULL; i++) { - if (retired_patch_writers[i].email != NULL) { - g_string_append_printf(str, " %s%s%s%s
", - retired_patch_writers[i].email, _(retired_patch_writers[i].name), - retired_patch_writers[i].role ? " (" : "", - retired_patch_writers[i].role ? _(retired_patch_writers[i].role) : "", - retired_patch_writers[i].role ? ")" : ""); - } else { - g_string_append_printf(str, " %s%s%s%s
", - _(retired_patch_writers[i].name), - retired_patch_writers[i].role ? " (" : "", - retired_patch_writers[i].role ? _(retired_patch_writers[i].role) : "", - retired_patch_writers[i].role ? ")" : ""); - } - } + add_developers(str, retired_patch_writers); g_string_append(str, "
"); /* Current Translators */ g_string_append_printf(str, "%s:
", _("Current Translators")); - for (i = 0; current_translators[i].language != NULL; i++) { - if (current_translators[i].email != NULL) { - g_string_append_printf(str, " %s (%s) - %s
", - _(current_translators[i].language), - current_translators[i].abbr, - current_translators[i].email, - _(current_translators[i].name)); - } else { - g_string_append_printf(str, " %s (%s) - %s
", - _(current_translators[i].language), - current_translators[i].abbr, - _(current_translators[i].name)); - } - } + add_translators(str, translators); g_string_append(str, "
"); /* Past Translators */ g_string_append_printf(str, "%s:
", _("Past Translators")); - for (i = 0; past_translators[i].language != NULL; i++) { - if (past_translators[i].email != NULL) { - g_string_append_printf(str, " %s (%s) - %s
", - _(past_translators[i].language), - past_translators[i].abbr, - past_translators[i].email, - _(past_translators[i].name)); - } else { - g_string_append_printf(str, " %s (%s) - %s
", - _(past_translators[i].language), - past_translators[i].abbr, - _(past_translators[i].name)); - } - } + add_translators(str, past_translators); g_string_append(str, "
"); g_string_append_printf(str, "%s
", _("Debugging Information")); diff -r a4dfc631b140 -r e17605cc2c0a pidgin/gtkimhtml.c --- a/pidgin/gtkimhtml.c Mon Oct 27 23:13:11 2008 +0000 +++ b/pidgin/gtkimhtml.c Wed Oct 29 21:38:53 2008 +0000 @@ -1974,7 +1974,7 @@ pos = strchr (t->values->str, *x); if (pos) - t = t->children [(unsigned int) pos - (unsigned int) t->values->str]; + t = t->children [pos - t->values->str]; else return; diff -r a4dfc631b140 -r e17605cc2c0a po/de.po --- a/po/de.po Mon Oct 27 23:13:11 2008 +0000 +++ b/po/de.po Wed Oct 29 21:38:53 2008 +0000 @@ -11,15 +11,15 @@ msgstr "" "Project-Id-Version: de\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-10-16 00:55-0700\n" -"PO-Revision-Date: 2008-10-02 23:07+0200\n" -"Last-Translator: Björn Voigt \n" +"POT-Creation-Date: 2008-10-28 17:46+0100\n" +"PO-Revision-Date: 2008-10-28 17:46+0100\n" +"Last-Translator: Jochen Kemnade \n" "Language-Team: Deutsch \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: KBabel 1.11.4\n" +"X-Generator: Lokalize 0.2\n" #. Translators may want to transliterate the name. #. It is not to be translated. @@ -243,9 +243,6 @@ msgid "You must give a name for the group to add." msgstr "Bitte geben Sie den Namen der Gruppe ein, die hinzugefügt werden soll." -msgid "A group with the name already exists." -msgstr "Es gibt schon eine Gruppe mit diesem Namen." - msgid "Add Group" msgstr "Gruppe hinzufügen" @@ -283,8 +280,8 @@ msgid "Blocked" msgstr "Blockiert" -msgid "View Log" -msgstr "Mitschnitt anzeigen" +msgid "Show when offline" +msgstr "Anzeigen, wenn im Offline-Modus" #, c-format msgid "Please enter the new name for %s" @@ -328,6 +325,9 @@ msgid "Toggle Tag" msgstr "Markierung umkehren" +msgid "View Log" +msgstr "Mitschnitt anzeigen" + #. General msgid "Nickname" msgstr "Spitzname" @@ -1128,7 +1128,6 @@ msgid "%s has sent you a message. (%s)" msgstr "%s hat Ihnen eine Nachricht gesendet. (%s)" -#, c-format msgid "Unknown pounce event. Please report this!" msgstr "Unbekanntes Alarm-Ereignis. Bitte berichten Sie dieses Problem!" @@ -1498,7 +1497,6 @@ "Wenn eine neue Unterhaltung eröffnet wird, fügt dieses Plugin die letzte " "Unterhaltung in die aktuelle Unterhaltung ein." -#, c-format msgid "Online" msgstr "Online" @@ -1841,7 +1839,6 @@ "Fehler beim Lesen vom Auflösungsprozess:\n" "%s" -#, c-format msgid "Resolver process exited without answering our request" msgstr "Auflösungsprozess hat sich beendet ohne die Anfrage zu beantworten" @@ -1932,7 +1929,6 @@ msgid "Transfer of file %s complete" msgstr "Übertragung der Datei %s ist komplett" -#, c-format msgid "File transfer complete" msgstr "Dateiübertragung ist komplett" @@ -1940,7 +1936,6 @@ msgid "You canceled the transfer of %s" msgstr "Sie haben die Dateiübertragung von %s abgebrochen" -#, c-format msgid "File transfer cancelled" msgstr "Dateiübertragung wurde abgebrochen" @@ -2148,7 +2143,6 @@ msgid "You are using %s, but this plugin requires %s." msgstr "Sie benutzen %s, aber dieses Plugin benötigt %s." -#, c-format msgid "This plugin has not defined an ID." msgstr "Dieses Plugin hat keine ID definiert." @@ -3044,7 +3038,6 @@ #. get_yahoo_status_from_purple_status() returns YAHOO_STATUS_CUSTOM for #. * the generic away state (YAHOO_STATUS_TYPE_AWAY) with no message #. Away stuff -#, c-format msgid "Away" msgstr "Abwesend" @@ -3936,7 +3929,6 @@ msgid "Extended Away" msgstr "Abwesend (erweitert)" -#, c-format msgid "Do Not Disturb" msgstr "Nicht stören" @@ -4711,220 +4703,166 @@ "%s ist auf der lokalen Liste, aber nicht auf der Serverliste. Möchten Sie, " "dass der Buddy hinzugefügt wird?" -#, c-format msgid "Unable to parse message" msgstr "Kann die Nachricht nicht parsen" -#, c-format msgid "Syntax Error (probably a client bug)" msgstr "Syntaxfehler (wahrscheinlich ein Client-Bug)" -#, c-format msgid "Invalid email address" msgstr "Ungültige E-Mail-Adresse" -#, c-format msgid "User does not exist" msgstr "Benutzer existiert nicht" -#, c-format msgid "Fully qualified domain name missing" msgstr "Der Fully Qualified Domain Name fehlt" -#, c-format msgid "Already logged in" msgstr "Schon angemeldet" -#, c-format msgid "Invalid username" msgstr "Ungültiger Benutzername" -#, c-format msgid "Invalid friendly name" msgstr "Ungültiger Freundesname" -#, c-format msgid "List full" msgstr "Liste voll" -#, c-format msgid "Already there" msgstr "Schon da" -#, c-format msgid "Not on list" msgstr "Nicht auf der Liste" -#, c-format msgid "User is offline" msgstr "Benutzer ist offline" -#, c-format msgid "Already in the mode" msgstr "Bereits in diesem Modus" -#, c-format msgid "Already in opposite list" msgstr "Bereits in der „Gegenteil-Liste“" -#, c-format msgid "Too many groups" msgstr "Zu viele Gruppen" -#, c-format msgid "Invalid group" msgstr "Ungültige Gruppe" -#, c-format msgid "User not in group" msgstr "Benutzer ist nicht in der Gruppe" -#, c-format msgid "Group name too long" msgstr "Name der Gruppe ist zu lang" -#, c-format msgid "Cannot remove group zero" msgstr "Kann die Gruppe „Null“ nicht entfernen" -#, c-format msgid "Tried to add a user to a group that doesn't exist" msgstr "" "Versuchte einen Benutzer zu einer nichtexistierenden Gruppe hinzuzufügen" -#, c-format msgid "Switchboard failed" msgstr "Vermittlung gescheitert" -#, c-format msgid "Notify transfer failed" msgstr "Übertragung der Benachrichtigung gescheitert" -#, c-format msgid "Required fields missing" msgstr "Notwendige Felder fehlen" -#, c-format msgid "Too many hits to a FND" msgstr "Zu viele Treffer zu einem FND" -#, c-format msgid "Not logged in" msgstr "Nicht angemeldet" -#, c-format msgid "Service temporarily unavailable" msgstr "Dienst momentan nicht verfügbar" -#, c-format msgid "Database server error" msgstr "Fehler des Datenbank-Servers" -#, c-format msgid "Command disabled" msgstr "Kommando abgeschaltet" -#, c-format msgid "File operation error" msgstr "Dateiverarbeitungsfehler" -#, c-format msgid "Memory allocation error" msgstr "Fehler bei der Speicheranforderung" -#, c-format msgid "Wrong CHL value sent to server" msgstr "Falscher CHL-Wert zum Server gesendet" -#, c-format msgid "Server busy" msgstr "Server beschäftigt" -#, c-format msgid "Server unavailable" msgstr "Server unerreichbar" -#, c-format msgid "Peer notification server down" msgstr "Peer-Benachrichtigungsserver nicht erreichbar" -#, c-format msgid "Database connect error" msgstr "Datenbank-Verbindungsfehler" -#, c-format msgid "Server is going down (abandon ship)" msgstr "Server fährt runter (melden Sie sich ab)" -#, c-format msgid "Error creating connection" msgstr "Fehler beim Herstellen der Verbindung" -#, c-format msgid "CVR parameters are either unknown or not allowed" msgstr "CVR-Parameter sind entweder unbekannt oder nicht erlaubt" -#, c-format msgid "Unable to write" msgstr "Schreiben nicht möglich" -#, c-format msgid "Session overload" msgstr "Sitzung überlastet" -#, c-format msgid "User is too active" msgstr "Benutzer ist zu aktiv" -#, c-format msgid "Too many sessions" msgstr "Zu viele Sitzungen" -#, c-format msgid "Passport not verified" msgstr "Passport (MSN Benutzerausweis) wurde nicht überprüft" -#, c-format msgid "Bad friend file" msgstr "Falsche Friends-Datei" -#, c-format msgid "Not expected" msgstr "Nicht erwartet" -#, c-format msgid "Friendly name changes too rapidly" msgstr "Benutzernamen werden zu oft geändert" -#, c-format msgid "Server too busy" msgstr "Server ist zu beschäftigt" -#, c-format msgid "Authentication failed" msgstr "Authentifizierung fehlgeschlagen" -#, c-format msgid "Not allowed when offline" msgstr "Nicht erlaubt im Offline-Modus" -#, c-format msgid "Not accepting new users" msgstr "Akzeptiert keine neuen Benutzer" -#, c-format msgid "Kids Passport without parental consent" msgstr "Kinder-Passwort ohne die Zustimmung der Eltern" -#, c-format msgid "Passport account not yet verified" msgstr "Passport-Konto wurde noch nicht überprüft" -#, c-format msgid "Passport account suspended" msgstr "Passport-Konto gesperrt" -#, c-format msgid "Bad ticket" msgstr "Falsches Ticket" @@ -5290,6 +5228,7 @@ msgid "%s just sent you a Nudge!" msgstr "%s hat Sie gerade angestoßen!" +#. char *adl = g_strndup(payload, len); #, c-format msgid "Unknown error (%d)" msgstr "Unbekannter Fehler (%d)" @@ -6155,7 +6094,6 @@ msgid "Error. SSL support is not installed." msgstr "Fehler. SSL ist nicht installiert." -#, c-format msgid "This conference has been closed. No more messages can be sent." msgstr "" "Diese Konferenz wurde geschlossen. Es können keine Nachrichten mehr gesendet " @@ -6425,23 +6363,18 @@ msgid "Screen Sharing" msgstr "Gemeinsamer Bildschirm" -#, c-format msgid "Free For Chat" msgstr "Bereit zum Chatten" -#, c-format msgid "Not Available" msgstr "Nicht verfügbar" -#, c-format msgid "Occupied" msgstr "Beschäftigt" -#, c-format msgid "Web Aware" msgstr "In Web" -#, c-format msgid "Invisible" msgstr "Unsichtbar" @@ -7137,7 +7070,6 @@ msgid "Attempting to connect to %s:%hu." msgstr "Verbindungsversuch mit %s:%hu." -#, c-format msgid "Attempting to connect via proxy server." msgstr "Verbindungsversuch über einen Proxyserver." @@ -7675,7 +7607,6 @@ msgstr "Verbindung verloren" #. Update the login progress status display -#, c-format msgid "Request token" msgstr "Anfragekürzel" @@ -8379,7 +8310,6 @@ msgid "
Channel Topic:
%s" msgstr "
Thema des Kanals:
%s" -#, c-format msgid "
Channel Modes: " msgstr "
Kanal-Modi: " @@ -8404,7 +8334,6 @@ msgid "Channel Public Keys List" msgstr "Liste der öffentlichen Schlüssel des Kanals" -#, c-format msgid "" "Channel authentication is used to secure the channel from unauthorized " "access. The authentication may be based on passphrase and digital " @@ -8809,7 +8738,6 @@ msgid "Your Current Mood" msgstr "Ihre momentane Stimmung" -#, c-format msgid "Normal" msgstr "Normal" @@ -9195,47 +9123,37 @@ msgid "No server statistics available" msgstr "Keine Serverstatistik verfügbar" -#, c-format msgid "Failure: Version mismatch, upgrade your client" msgstr "Fehler: Unterschiedliche Version, aktualisieren Sie Ihren Client" -#, c-format msgid "Failure: Remote does not trust/support your public key" msgstr "" "Fehler: Die entfernte Seite vertraut Ihrem öffentlichen Schlüssel nicht" -#, c-format msgid "Failure: Remote does not support proposed KE group" msgstr "" "Fehler: Entferntes Programm unterstützt nicht die vorgeschlagen KE-Gruppe" -#, c-format msgid "Failure: Remote does not support proposed cipher" msgstr "" "Fehler: Entferntes Programm unterstützt die vorgeschlagene Cipher nicht" -#, c-format msgid "Failure: Remote does not support proposed PKCS" msgstr "Fehler: Entferntes Programm unterstützt die vorgeschlagene PKCS nicht" -#, c-format msgid "Failure: Remote does not support proposed hash function" msgstr "" "Fehler: Entferntes Programm unterstützt die vorgeschlagen Hashfunktion nicht" -#, c-format msgid "Failure: Remote does not support proposed HMAC" msgstr "Fehler: Entferntes Programm unterstützt das vorgeschlagene HMAC nicht" -#, c-format msgid "Failure: Incorrect signature" msgstr "Fehler: Falsche Signatur" -#, c-format msgid "Failure: Invalid cookie" msgstr "Fehler: Ungültiger Cookie" -#, c-format msgid "Failure: Authentication failed" msgstr "Fehler: Authentifizierung fehlgeschlagen" @@ -9332,7 +9250,6 @@ msgid "Warning of %s not allowed." msgstr "Verwarnung von %s nicht erlaubt." -#, c-format msgid "A message has been dropped, you are exceeding the server speed limit." msgstr "" "Eine Nachricht ging verloren. Sie überschreiten die Geschwindigkeitsgrenze " @@ -9356,39 +9273,30 @@ "Eine Nachricht von %s hat Sie nicht erreicht, da sie zu schnell gesendet " "wurde." -#, c-format msgid "Failure." msgstr "Fehler." -#, c-format msgid "Too many matches." msgstr "Zu viele Übereinstimmungen." -#, c-format msgid "Need more qualifiers." msgstr "Benötige mehr Angaben." -#, c-format msgid "Dir service temporarily unavailable." msgstr "Verzeichnis-Dienst ist zur Zeit nicht verfügbar." -#, c-format msgid "Email lookup restricted." msgstr "E-Mail-Suche eingeschränkt." -#, c-format msgid "Keyword ignored." msgstr "Stichwort ignoriert." -#, c-format msgid "No keywords." msgstr "Keine Stichwörter." -#, c-format msgid "User has no directory information." msgstr "Der Benutzer hat kein Profil." -#, c-format msgid "Country not supported." msgstr "Land nicht unterstützt." @@ -9396,19 +9304,15 @@ msgid "Failure unknown: %s." msgstr "Unbekannter Fehler: %s." -#, c-format msgid "Incorrect username or password." msgstr "Ungültiger Benutzername oder Passwort." -#, c-format msgid "The service is temporarily unavailable." msgstr "Der Dienst ist zur Zeit nicht verfügbar." -#, c-format msgid "Your warning level is currently too high to log in." msgstr "Ihre Warnstufe ist zur Zeit zu hoch, um sich anzumelden." -#, c-format msgid "" "You have been connecting and disconnecting too frequently. Wait ten minutes " "and try again. If you continue to try, you will need to wait even longer." @@ -10252,29 +10156,24 @@ msgstr " (%s)" #. 10053 -#, c-format msgid "Connection interrupted by other software on your computer." msgstr "" "Die Verbindung wurde von einer anderen Software auf ihrem Computer " "unterbrochen." #. 10054 -#, c-format msgid "Remote host closed connection." msgstr "Der entfernte Host hat die Verbindung beendet." #. 10060 -#, c-format msgid "Connection timed out." msgstr "Verbindungsabbruch wegen Zeitüberschreitung." #. 10061 -#, c-format msgid "Connection refused." msgstr "Verbindung abgelehnt." #. 10048 -#, c-format msgid "Address already in use." msgstr "Adresse wird bereits benutzt." @@ -10483,9 +10382,6 @@ msgid "Hide when offline" msgstr "Verstecken, wenn im Offline-Modus" -msgid "Show when offline" -msgstr "Anzeigen, wenn im Offline-Modus" - msgid "_Alias..." msgstr "_Alias..." @@ -10883,9 +10779,6 @@ msgid "SSL Servers" msgstr "SSL-Server" -msgid "Network disconnected" -msgstr "vom Netzwerk abgemeldet" - msgid "Unknown command." msgstr "Unbekanntes Kommando." @@ -11228,8 +11121,11 @@ msgid "Fatal Error" msgstr "Schwerer Fehler" -msgid "developer" -msgstr "Entwickler" +msgid "bug master" +msgstr "Bug-Master" + +msgid "artist" +msgstr "Künstler" #. feel free to not translate this msgid "Ka-Hing Cheung" @@ -11238,11 +11134,8 @@ msgid "support" msgstr "Support" -msgid "support/QA" -msgstr "Support/Qualitätssicherung" - -msgid "developer & webmaster" -msgstr "Entwickler & Webmaster" +msgid "webmaster" +msgstr "Webmaster" msgid "Senior Contributor/QA" msgstr "Senior-Beitragender/QA" @@ -11260,8 +11153,11 @@ msgid "hacker and designated driver [lazy bum]" msgstr "Grafische Benutzeroberfläche" -msgid "XMPP developer" -msgstr "XMPP-Entwickler" +msgid "support/QA" +msgstr "Support/Qualitätssicherung" + +msgid "XMPP" +msgstr "XMPP" msgid "original author" msgstr "Originalautor" @@ -11508,7 +11404,6 @@ "geschützt. Die Datei 'COPYRIGHT' enthält die komplette Liste der " "Mitwirkenden. Wir übernehmen keine Haftung für dieses Programm.

" -#, c-format msgid "IRC: #pidgin on irc.freenode.net

" msgstr "IRC: #pidgin auf irc.freenode.net

" @@ -11524,9 +11419,6 @@ msgid "Retired Crazy Patch Writers" msgstr "Zurückgetretene verrückte Patchschreiber" -msgid "Artists" -msgstr "Künstler" - msgid "Current Translators" msgstr "Aktuelle Übersetzer" @@ -11879,11 +11771,9 @@ msgid "Save Image" msgstr "Bild speichern" -#, c-format msgid "_Save Image..." msgstr "Bild _speichern..." -#, c-format msgid "_Add Custom Smiley..." msgstr "Benutzerdefinierten Smiley _hinzufügen..." @@ -12608,27 +12498,21 @@ msgid "Sound Selection" msgstr "Klang-Auswahl" -#, c-format msgid "Quietest" msgstr "Am leisesten" -#, c-format msgid "Quieter" msgstr "Leiser" -#, c-format msgid "Quiet" msgstr "Leise" -#, c-format msgid "Loud" msgstr "Laut" -#, c-format msgid "Louder" msgstr "Lauter" -#, c-format msgid "Loudest" msgstr "Am lautesten" @@ -13440,10 +13324,10 @@ msgstr "Zeichne eine Markierunglinie in " msgid "_IM windows" -msgstr "_IM-Fenstern" +msgstr "_IM-Fenster" msgid "C_hat windows" -msgstr "C_hat-Fenstern" +msgstr "C_hat-Fenster" msgid "" "A music messaging session has been requested. Please click the MM icon to " @@ -13525,6 +13409,9 @@ msgid "Set window manager \"_URGENT\" hint" msgstr "Setze den „_URGENT“-Hinweis für den Window-Manager" +msgid "_Flash window" +msgstr "Fenster _blinken lassen" + #. Raise window method button msgid "R_aise conversation window" msgstr "G_esprächsfenster in den Vordergrund bringen" @@ -13635,7 +13522,6 @@ msgid "Select Color" msgstr "Farbe auswählen" -#, c-format msgid "Select Interface Font" msgstr "Schriftart wählen" @@ -13862,7 +13748,6 @@ msgid "Timestamp Format Options" msgstr "Zeitstempelformat-Optionen" -#, c-format msgid "_Force 24-hour time format" msgstr "_Erzwinge 24-Stunden Zeitformat"