Mercurial > pidgin
changeset 31498:9e9abd65b840
propagate from branch 'im.pidgin.pidgin' (head ece03c16966f58e1db43e5c20fe35a6707b468d2)
to branch 'im.pidgin.pidgin.mxit' (head 524638c79d3d0540b9343247091acf620ef2c733)
author | andrew.victor@mxit.com |
---|---|
date | Thu, 31 Mar 2011 19:32:34 +0000 |
parents | acd92b7d8511 (diff) a9e077fb65e9 (current diff) |
children | a769e6da0a8e |
files | |
diffstat | 13 files changed, 498 insertions(+), 176 deletions(-) [+] |
line wrap: on
line diff
--- a/ChangeLog Thu Mar 31 04:41:27 2011 +0000 +++ b/ChangeLog Thu Mar 31 19:32:34 2011 +0000 @@ -42,6 +42,13 @@ an ICQ account's settings by using a comma-delimited list. (Dmitry Utkin (#13496) + MXit: + * Support for an Invite Message when adding a buddy. + * Fix bug when splitting a long messages with lots of links. + * Fix detection of new kick message from a MultiMX room. + * Fix crash caused by fast-queue timer not being removed on session + close. (broken in 2.7.11) + Plugins: * The Voice/Video Settings plugin now includes the ability to test microphone settings. (Jakub Adam) (#13182)
--- a/libpurple/protocols/mxit/actions.c Thu Mar 31 04:41:27 2011 +0000 +++ b/libpurple/protocols/mxit/actions.c Thu Mar 31 19:32:34 2011 +0000 @@ -186,6 +186,43 @@ g_string_append( attributes, attrib ); acount++; +#if 0 + /* update about me */ + name = purple_request_fields_get_string( fields, "aboutme" ); + if ( !name ) + profile->aboutme[0] = '\0'; + else + g_strlcpy( profile->aboutme, name, sizeof( profile->aboutme ) ); + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_ABOUTME, CP_PROFILE_TYPE_UTF8, profile->aboutme ); + g_string_append( attributes, attrib ); + acount++; + + /* update where am i */ + name = purple_request_fields_get_string( fields, "whereami" ); + if ( !name) + profile->whereami[0] = '\0'; + else + g_strlcpy( profile->whereami, name, sizeof( profile->whereami ) ); + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_WHEREAMI, CP_PROFILE_TYPE_UTF8, profile->whereami ); + g_string_append( attributes, attrib ); + acount++; +#endif + + /* update flags */ + field = purple_request_fields_get_field( fields, "searchable" ); + if ( purple_request_field_bool_get_value( field ) ) /* is searchable -> clear not-searchable flag */ + profile->flags &= ~CP_PROF_NOT_SEARCHABLE; + else + profile->flags |= CP_PROF_NOT_SEARCHABLE; + field = purple_request_fields_get_field( fields, "suggestable" ); + if ( purple_request_field_bool_get_value( field ) ) /* is suggestable -> clear not-suggestable flag */ + profile->flags &= ~CP_PROF_NOT_SUGGESTABLE; + else + profile->flags |= CP_PROF_NOT_SUGGESTABLE; + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%i", CP_PROFILE_FLAGS, CP_PROFILE_TYPE_LONG, profile->flags); + g_string_append( attributes, attrib ); + acount++; + /* send the profile update to MXit */ mxit_send_extprofile_update( session, session->encpwd, acount, attributes->str ); g_string_free( attributes, TRUE ); @@ -209,7 +246,6 @@ struct MXitProfile* profile = session->profile; PurpleRequestFields* fields = NULL; - PurpleRequestFieldGroup* group = NULL; PurpleRequestField* field = NULL; purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_action_profile\n" ); @@ -222,65 +258,92 @@ } fields = purple_request_fields_new(); - group = purple_request_field_group_new( NULL ); - purple_request_fields_add_group( fields, group ); + + /* Security information - PIN, etc */ + { + PurpleRequestFieldGroup* security_group = purple_request_field_group_new( "PIN" ); -#if 0 - /* UID (read-only) */ - if ( session->uid ) { - field = purple_request_field_string_new( "mxitid", _( "Your UID" ), session->uid, FALSE ); - purple_request_field_string_set_editable( field, FALSE ); - purple_request_field_group_add_field( group, field ); + /* pin */ + field = purple_request_field_string_new( "pin", _( "PIN" ), session->acc->password, FALSE ); + purple_request_field_string_set_masked( field, TRUE ); + purple_request_field_group_add_field( security_group, field ); + + field = purple_request_field_string_new( "pin2", _( "Verify PIN" ), session->acc->password, FALSE ); + purple_request_field_string_set_masked( field, TRUE ); + purple_request_field_group_add_field( security_group, field ); + + purple_request_fields_add_group( fields, security_group ); } -#endif - /* pin */ - field = purple_request_field_string_new( "pin", _( "PIN" ), session->acc->password, FALSE ); - purple_request_field_string_set_masked( field, TRUE ); - purple_request_field_group_add_field( group, field ); - field = purple_request_field_string_new( "pin2", _( "Verify PIN" ), session->acc->password, FALSE ); - purple_request_field_string_set_masked( field, TRUE ); - purple_request_field_group_add_field( group, field ); + /* Public information - what other users can see */ + { + PurpleRequestFieldGroup* public_group = purple_request_field_group_new( "Public information" ); + + /* display name */ + field = purple_request_field_string_new( "name", _( "Display Name" ), profile->nickname, FALSE ); + purple_request_field_group_add_field( public_group, field ); - /* display name */ - field = purple_request_field_string_new( "name", _( "Display Name" ), profile->nickname, FALSE ); - purple_request_field_group_add_field( group, field ); + /* birthday */ + field = purple_request_field_string_new( "bday", _( "Birthday" ), profile->birthday, FALSE ); + purple_request_field_group_add_field( public_group, field ); + if ( profile->flags & CP_PROF_DOBLOCKED ) + purple_request_field_string_set_editable( field, FALSE ); - /* birthday */ - field = purple_request_field_string_new( "bday", _( "Birthday" ), profile->birthday, FALSE ); - purple_request_field_group_add_field( group, field ); - if ( profile->flags & CP_PROF_DOBLOCKED ) - purple_request_field_string_set_editable( field, FALSE ); + /* gender */ + field = purple_request_field_choice_new( "male", _( "Gender" ), ( profile->male ) ? 1 : 0 ); + purple_request_field_choice_add( field, _( "Female" ) ); /* 0 */ + purple_request_field_choice_add( field, _( "Male" ) ); /* 1 */ + purple_request_field_group_add_field( public_group, field ); + + /* first name */ + field = purple_request_field_string_new( "firstname", _( "First Name" ), profile->firstname, FALSE ); + purple_request_field_group_add_field( public_group, field ); - /* gender */ - field = purple_request_field_choice_new( "male", _( "Gender" ), ( profile->male ) ? 1 : 0 ); - purple_request_field_choice_add( field, _( "Female" ) ); /* 0 */ - purple_request_field_choice_add( field, _( "Male" ) ); /* 1 */ - purple_request_field_group_add_field( group, field ); + /* last name */ + field = purple_request_field_string_new( "lastname", _( "Last Name" ), profile->lastname, FALSE ); + purple_request_field_group_add_field( public_group, field ); + + /* about me */ + field = purple_request_field_string_new( "aboutme", _( "About Me" ), profile->aboutme, FALSE); + purple_request_field_group_add_field( public_group, field ); - /* hidden */ - field = purple_request_field_bool_new( "hidden", _( "Hide my number" ), profile->hidden ); - purple_request_field_group_add_field( group, field ); + /* where I live */ + field = purple_request_field_string_new( "whereami", _( "Where I Live" ), profile->whereami, FALSE); + purple_request_field_group_add_field( public_group, field ); + + purple_request_fields_add_group( fields, public_group ); + } - /* title */ - field = purple_request_field_string_new( "title", _( "Title" ), profile->title, FALSE ); - purple_request_field_group_add_field( group, field ); + /* Private information - what only MXit can see */ + { + PurpleRequestFieldGroup* private_group = purple_request_field_group_new( "Private information" ); + + /* title */ + field = purple_request_field_string_new( "title", _( "Title" ), profile->title, FALSE ); + purple_request_field_group_add_field( private_group, field ); - /* first name */ - field = purple_request_field_string_new( "firstname", _( "First Name" ), profile->firstname, FALSE ); - purple_request_field_group_add_field( group, field ); + /* email */ + field = purple_request_field_string_new( "email", _( "Email" ), profile->email, FALSE ); + purple_request_field_group_add_field( private_group, field ); - /* last name */ - field = purple_request_field_string_new( "lastname", _( "Last Name" ), profile->lastname, FALSE ); - purple_request_field_group_add_field( group, field ); + /* mobile number */ + field = purple_request_field_string_new( "mobilenumber", _( "Mobile Number" ), profile->mobilenr, FALSE ); + purple_request_field_group_add_field( private_group, field ); - /* email */ - field = purple_request_field_string_new( "email", _( "Email" ), profile->email, FALSE ); - purple_request_field_group_add_field( group, field ); + /* hidden number */ + field = purple_request_field_bool_new( "hidden", _( "Hide my number" ), profile->hidden ); + purple_request_field_group_add_field( private_group, field ); - /* mobile number */ - field = purple_request_field_string_new( "mobilenumber", _( "Mobile Number" ), profile->mobilenr, FALSE ); - purple_request_field_group_add_field( group, field ); + /* is searchable */ + field = purple_request_field_bool_new( "searchable", _( "Can be searched" ), ( ( profile->flags & CP_PROF_NOT_SEARCHABLE ) == 0) ); + purple_request_field_group_add_field( private_group, field ); + + /* is suggestable */ + field = purple_request_field_bool_new( "suggestable", _( "Can be suggested" ), ( ( profile->flags & CP_PROF_NOT_SUGGESTABLE ) == 0 ) ); + purple_request_field_group_add_field( private_group, field ); + + purple_request_fields_add_group( fields, private_group ); + } /* (reference: "libpurple/request.h") */ purple_request_fields( gc, _( "Profile" ), _( "Update your Profile" ), _( "Here you can update your MXit profile" ), fields, _( "Set" ), @@ -314,7 +377,7 @@ { char version[256]; - g_snprintf( version, sizeof( version ), + g_snprintf( version, sizeof( version ), "MXit Client Protocol v%i.%i\n\n" "Author:\nPieter Loubser\n\n" "Contributors:\nAndrew Victor\n\n" @@ -326,6 +389,59 @@ /*------------------------------------------------------------------------ + * Request list of suggested friends. + * + * @param action The action object + */ +static void mxit_cb_suggested_friends( PurplePluginAction* action ) +{ + PurpleConnection* gc = (PurpleConnection*) action->context; + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + const char* profilelist[] = { + CP_PROFILE_BIRTHDATE, CP_PROFILE_GENDER, CP_PROFILE_FULLNAME, CP_PROFILE_FIRSTNAME, + CP_PROFILE_LASTNAME, CP_PROFILE_REGCOUNTRY, CP_PROFILE_STATUS, CP_PROFILE_AVATAR }; + + mxit_send_suggest_friends( session, 20, ARRAY_SIZE( profilelist ), profilelist ); +} + + +/*------------------------------------------------------------------------ + * Perform contact search. + * + * @param action The action object + */ +static void mxit_user_search_cb( PurpleConnection *gc, const char *input ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + const char* profilelist[] = { + CP_PROFILE_BIRTHDATE, CP_PROFILE_GENDER, CP_PROFILE_FULLNAME, CP_PROFILE_FIRSTNAME, + CP_PROFILE_LASTNAME, CP_PROFILE_REGCOUNTRY, CP_PROFILE_STATUS, CP_PROFILE_AVATAR }; + + mxit_send_suggest_search( session, 20, input, ARRAY_SIZE( profilelist ), profilelist ); +} + + +/*------------------------------------------------------------------------ + * Display the search input form. + * + * @param action The action object + */ +static void mxit_cb_search_begin( PurplePluginAction* action ) +{ + PurpleConnection* gc = (PurpleConnection*) action->context; + + purple_request_input( gc, _( "Search for user" ), + _( "Search for a MXit contact" ), + _( "Type search information" ), + NULL, FALSE, FALSE, NULL, + _("_Search"), G_CALLBACK( mxit_user_search_cb ), + _("_Cancel"), NULL, + purple_connection_get_account( gc ), NULL, NULL, + gc); +} + + +/*------------------------------------------------------------------------ * Associate actions with the MXit plugin. * * @param plugin The MXit protocol plugin @@ -349,6 +465,14 @@ action = purple_plugin_action_new( _( "About..." ), mxit_cb_action_about ); m = g_list_append( m, action ); + /* suggested friends */ + action = purple_plugin_action_new( _( "Suggested friends..." ), mxit_cb_suggested_friends ); + m = g_list_append( m, action ); + + /* search for users */ + action = purple_plugin_action_new( _( "Search for Users..." ), mxit_cb_search_begin ); + m = g_list_append( m, action ); + return m; }
--- a/libpurple/protocols/mxit/login.c Thu Mar 31 04:41:27 2011 +0000 +++ b/libpurple/protocols/mxit/login.c Thu Mar 31 19:32:34 2011 +0000 @@ -141,9 +141,9 @@ } /* This timer might already exist if we're registering a new account */ - if ( session->q_timer == 0 ) { + if ( session->q_slow_timer_id == 0 ) { /* start the tx queue manager timer */ - session->q_timer = purple_timeout_add_seconds( 2, mxit_manage_queue_slow, session ); + session->q_slow_timer_id = purple_timeout_add_seconds( 2, mxit_manage_queue_slow, session ); } }
--- a/libpurple/protocols/mxit/markup.c Thu Mar 31 04:41:27 2011 +0000 +++ b/libpurple/protocols/mxit/markup.c Thu Mar 31 19:32:34 2011 +0000 @@ -235,7 +235,6 @@ */ static void mxit_show_split_message( struct RXMsgData* mx ) { - const char* cont = "<font color=\"#999999\">continuing...</font>\n"; GString* msg = NULL; char* ch = NULL; int pos = 0; @@ -245,7 +244,6 @@ int l_gt = 0; int stop = 0; int tags = 0; - int segs = 0; gboolean intag = FALSE; /* @@ -319,21 +317,20 @@ stop--; } - /* build the string */ - if ( segs ) - g_string_prepend( msg, cont ); - /* push message to pidgin */ serv_got_im( mx->session->con, mx->from, msg->str, mx->flags, mx->timestamp ); g_string_free( msg, TRUE ); msg = NULL; + /* next part need this flag set */ + mx->flags |= PURPLE_MESSAGE_RAW; + tags = 0; - segs++; start = stop + 1; + pos = start; } - - pos++; + else + pos++; } if ( start != pos ) { @@ -343,8 +340,6 @@ ch[pos] = '\0'; msg = g_string_new( &ch[start] ); ch[pos] = '\n'; - if ( segs ) - g_string_prepend( msg, cont ); /* push message to pidgin */ serv_got_im( mx->session->con, mx->from, msg->str, mx->flags, mx->timestamp );
--- a/libpurple/protocols/mxit/multimx.c Thu Mar 31 04:41:27 2011 +0000 +++ b/libpurple/protocols/mxit/multimx.c Thu Mar 31 19:32:34 2011 +0000 @@ -177,22 +177,13 @@ /*------------------------------------------------------------------------ * Another user has join the GroupChat, add them to the member-list. * - * @param session The MXit session object - * @param multimx The MultiMX room object + * @param convo The Conversation object * @param nickname The nickname of the user who joined the room */ -static void member_added(struct MXitSession* session, struct multimx* multimx, const char* nickname) +static void member_added(PurpleConversation* convo, const char* nickname) { - PurpleConversation *convo; - purple_debug_info(MXIT_PLUGIN_ID, "member_added: '%s'\n", nickname); - convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc); - if (convo == NULL) { - purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname); - return; - } - purple_conv_chat_add_user(PURPLE_CONV_CHAT(convo), nickname, NULL, PURPLE_CBFLAGS_NONE, TRUE); } @@ -200,79 +191,61 @@ /*------------------------------------------------------------------------ * Another user has left the GroupChat, remove them from the member-list. * - * @param session The MXit session object - * @param multimx The MultiMX room object + * @param convo The Conversation object * @param nickname The nickname of the user who left the room */ -static void member_removed(struct MXitSession* session, struct multimx* multimx, const char* nickname) +static void member_removed(PurpleConversation* convo, const char* nickname) { - PurpleConversation *convo; - purple_debug_info(MXIT_PLUGIN_ID, "member_removed: '%s'\n", nickname); - convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc); - if (convo == NULL) { - purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname); - return; - } - purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo), nickname, NULL); } /*------------------------------------------------------------------------ - * A user was kicked from the GroupChat. + * A user was kicked from the GroupChat, remove them from the member-list. * - * @param session The MXit session object - * @param multimx The MultiMX room object + * @param convo The Conversation object * @param nickname The nickname of the user who was kicked */ -static void member_kicked(struct MXitSession* session, struct multimx* multimx, const char* nickname) +static void member_kicked(PurpleConversation* convo, const char* nickname) { - PurpleConversation *convo; - purple_debug_info(MXIT_PLUGIN_ID, "member_kicked: '%s'\n", nickname); - convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc); - if (convo == NULL) { - purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname); - return; - } + purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo), nickname, _("was kicked")); +} + - /* who was kicked? - compare to our original nickname */ - if (purple_utf8_strcasecmp(nickname, multimx->nickname) == 0) - { - /* you were kicked */ - purple_conv_chat_write(PURPLE_CONV_CHAT(convo), "MXit", _("You have been kicked from this MultiMX."), PURPLE_MESSAGE_SYSTEM, time(NULL)); - purple_conv_chat_clear_users(PURPLE_CONV_CHAT(convo)); - serv_got_chat_left(session->con, multimx->chatid); - } - else - purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo), nickname, _("was kicked")); +/*------------------------------------------------------------------------ + * You were kicked from the GroupChat. + * + * @param convo The Conversation object + * @param session The MXit session object + * @param multimx The MultiMX room object + */ +static void you_kicked(PurpleConversation* convo, struct MXitSession* session, struct multimx* multimx) +{ + purple_debug_info(MXIT_PLUGIN_ID, "you_kicked\n"); + + purple_conv_chat_write(PURPLE_CONV_CHAT(convo), "MXit", _("You have been kicked from this MultiMX."), PURPLE_MESSAGE_SYSTEM, time(NULL)); + purple_conv_chat_clear_users(PURPLE_CONV_CHAT(convo)); + serv_got_chat_left(session->con, multimx->chatid); } /*------------------------------------------------------------------------ * Update the full GroupChat member list. * - * @param session The MXit session object - * @param multimx The MultiMX room object + * @param convo The Conversation object * @param data The nicknames of the users in the room (separated by \n) */ -static void member_update(struct MXitSession* session, struct multimx* multimx, char* data) +static void member_update(PurpleConversation* convo, char* data) { - PurpleConversation *convo; gchar** userlist; int i = 0; purple_debug_info(MXIT_PLUGIN_ID, "member_update: '%s'\n", data); - convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc); - if (convo == NULL) { - purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname); - return; - } - /* Clear list */ purple_conv_chat_clear_users(PURPLE_CONV_CHAT(convo)); @@ -402,27 +375,38 @@ /* Must be a service message */ char* ofs; + PurpleConversation* convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, mx->session->acc); + if (convo == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname); + return; + } + /* Determine if somebody has joined or left - update member-list */ if ((ofs = strstr(msg, " has joined")) != NULL) { /* Somebody has joined */ *ofs = '\0'; - member_added(mx->session, multimx, msg); + member_added(convo, msg); mx->processed = TRUE; } else if ((ofs = strstr(msg, " has left")) != NULL) { /* Somebody has left */ *ofs = '\0'; - member_removed(mx->session, multimx, msg); + member_removed(convo, msg); mx->processed = TRUE; } else if ((ofs = strstr(msg, " has been kicked")) != NULL) { /* Somebody has been kicked */ *ofs = '\0'; - member_kicked(mx->session, multimx, msg); + member_kicked(convo, msg); + mx->processed = TRUE; + } + else if (strcmp(msg, "You have been kicked.") == 0) { + /* You have been kicked */ + you_kicked(convo, mx->session, multimx); mx->processed = TRUE; } else if (g_str_has_prefix(msg, "The following users are in this MultiMx:") == TRUE) { - member_update(mx->session, multimx, msg + strlen("The following users are in this MultiMx:") + 1); + member_update(convo, msg + strlen("The following users are in this MultiMx:") + 1); mx->processed = TRUE; } else {
--- a/libpurple/protocols/mxit/mxit.c Thu Mar 31 04:41:27 2011 +0000 +++ b/libpurple/protocols/mxit/mxit.c Thu Mar 31 19:32:34 2011 +0000 @@ -565,7 +565,7 @@ struct MXitSession* session = (struct MXitSession*) gc->proto_data; const char* profilelist[] = { CP_PROFILE_BIRTHDATE, CP_PROFILE_GENDER, CP_PROFILE_FULLNAME, CP_PROFILE_FIRSTNAME, CP_PROFILE_LASTNAME, CP_PROFILE_REGCOUNTRY, CP_PROFILE_LASTSEEN, - CP_PROFILE_STATUS, CP_PROFILE_AVATAR }; + CP_PROFILE_STATUS, CP_PROFILE_AVATAR, CP_PROFILE_WHEREAMI, CP_PROFILE_ABOUTME }; purple_debug_info( MXIT_PLUGIN_ID, "mxit_get_info: '%s'\n", who ); @@ -628,7 +628,7 @@ return; /* send a new invite */ - mxit_send_invite( session, contact->username, contact->alias, contact->groupname ); + mxit_send_invite( session, contact->username, contact->alias, contact->groupname, NULL ); } @@ -664,7 +664,7 @@ /*========================================================================================================================*/ static PurplePluginProtocolInfo proto_info = { - OPT_PROTO_REGISTER_NOSCREENNAME | OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_IM_IMAGE, /* options */ + OPT_PROTO_REGISTER_NOSCREENNAME | OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_IM_IMAGE | OPT_PROTO_INVITE_MESSAGE, /* options */ NULL, /* user_splits */ NULL, /* protocol_options */ { /* icon_spec */ @@ -692,7 +692,7 @@ mxit_set_status, /* set_status */ NULL, /* set_idle */ NULL, /* change_passwd */ - mxit_add_buddy, /* add_buddy [roster.c] */ + NULL, /* add_buddy [roster.c] */ NULL, /* add_buddies */ mxit_remove_buddy, /* remove_buddy [roster.c] */ NULL, /* remove_buddies */ @@ -743,7 +743,7 @@ mxit_get_moods, /* get_moods */ NULL, /* set_public_alias */ NULL, /* get_public_alias */ - NULL, /* add_buddy_with_invite */ + mxit_add_buddy, /* add_buddy_with_invite */ NULL /* add_buddies_with_invite */ };
--- a/libpurple/protocols/mxit/mxit.h Thu Mar 31 04:41:27 2011 +0000 +++ b/libpurple/protocols/mxit/mxit.h Thu Mar 31 19:32:34 2011 +0000 @@ -162,7 +162,8 @@ struct tx_queue queue; /* transmit packet queue (FIFO mode) */ gint64 last_tx; /* timestamp of last packet sent */ int outack; /* outstanding ack packet */ - guint q_timer; /* timer handler for managing queue */ + guint q_slow_timer_id; /* timer handle for slow tx queue */ + guint q_fast_timer_id; /* timer handle for fast tx queue */ /* receive */ char rx_lbuf[16]; /* receive byte buffer (socket packet length) */
--- a/libpurple/protocols/mxit/profile.c Thu Mar 31 04:41:27 2011 +0000 +++ b/libpurple/protocols/mxit/profile.c Thu Mar 31 19:32:34 2011 +0000 @@ -23,6 +23,9 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#define _XOPEN_SOURCE +#include <time.h> + #include "internal.h" #include "purple.h" @@ -34,7 +37,7 @@ /*------------------------------------------------------------------------ * Returns true if it is a valid date. * - * @param bday Date-of-Birth string + * @param bday Date-of-Birth string (YYYY-MM-DD) * @return TRUE if valid, else FALSE */ gboolean validateDate( const char* bday ) @@ -101,6 +104,40 @@ /*------------------------------------------------------------------------ + * Calculate an Age from the date-of-birth. + * + * @param date Date-of-Birth string (YYYY-MM-DD) + * @return The age + */ +static int calculateAge( const char* date ) +{ + time_t t; + struct tm now, bdate; + int age; + + if ( ( !date ) || ( strlen( date ) == 0 ) ) + return 0; + + /* current time */ + t = time(NULL); + localtime_r( &t, &now ); + + /* decode hdate */ + memset( &bdate, 0, sizeof( struct tm ) ); + strptime( date, "%Y-%m-%d", &bdate ); + + /* calculate difference */ + age = now.tm_year - bdate.tm_year; + if ( now.tm_mon < bdate.tm_mon ) /* is before month of birth */ + age--; + else if ( (now.tm_mon == bdate.tm_mon ) && ( now.tm_mday < bdate.tm_mday ) ) /* before birthday in current month */ + age--; + + return age; +} + + +/*------------------------------------------------------------------------ * Returns timestamp field in date & time format (DD-MM-YYYY HH:MM:SS) * * @param msecs The timestamps (milliseconds since epoch) @@ -120,9 +157,9 @@ /*------------------------------------------------------------------------ * Display the profile information. * - * @param session The MXit session object - * @param username The username who's profile information this is - * @param profile The profile + * @param session The MXit session object + * @param username The username who's profile information this is + * @param profile The profile */ void mxit_show_profile( struct MXitSession* session, const char* username, struct MXitProfile* profile ) { @@ -146,6 +183,11 @@ purple_notify_user_info_add_pair( info, _( "Last Name" ), profile->lastname ); purple_notify_user_info_add_pair( info, _( "Country" ), profile->regcountry ); + if ( strlen( profile->aboutme ) > 0 ) + purple_notify_user_info_add_pair( info, _( "About Me" ), profile->aboutme ); + if ( strlen( profile->whereami ) > 0 ) + purple_notify_user_info_add_pair( info, _( "Where I Live" ), profile->whereami ); + purple_notify_user_info_add_section_break( info ); if ( contact ) { @@ -176,3 +218,62 @@ purple_notify_userinfo( session->con, username, info, NULL, NULL ); purple_notify_user_info_destroy( info ); } + +/*------------------------------------------------------------------------ + * Display the profiles of search results. + * + * @param session The MXit session object + * @param searchType The type of search (CP_SUGGEST_*) + * @param maxResults The maximum number of results + * @param entries The list of profile entries + */ +void mxit_show_search_results( struct MXitSession* session, int searchType, int maxResults, GList* entries ) +{ + PurpleNotifySearchResults* results; + PurpleNotifySearchColumn* column; + gchar* text; + + if ( !entries ) { + mxit_popup( PURPLE_NOTIFY_MSG_INFO, _( "No results" ), _( "No users found." ) ); + return; + } + + results = purple_notify_searchresults_new(); + if ( !results ) + return; + + /* define columns */ + column = purple_notify_searchresults_column_new( _( "UserId" ) ); + purple_notify_searchresults_column_add( results, column ); + column = purple_notify_searchresults_column_new( _( "Display Name" ) ); + purple_notify_searchresults_column_add( results, column ); + column = purple_notify_searchresults_column_new( _( "Gender" ) ); + purple_notify_searchresults_column_add( results, column ); + column = purple_notify_searchresults_column_new( _( "Age" ) ); + purple_notify_searchresults_column_add( results, column ); + column = purple_notify_searchresults_column_new( _( "Where I live" ) ); + purple_notify_searchresults_column_add( results, column ); + + while (entries != NULL) { + struct MXitProfile* profile = ( struct MXitProfile *) entries->data; + GList* row; + + /* column values */ + row = g_list_append( NULL, g_strdup( profile->userid ) ); + row = g_list_append( row, g_strdup( profile->nickname ) ); + row = g_list_append( row, g_strdup( profile->male ? "Male" : "Female" ) ); + row = g_list_append( row, g_strdup_printf( "%i", calculateAge( profile->birthday ) ) ); + row = g_list_append( row, g_strdup( profile->whereami ) ); + + purple_notify_searchresults_row_add( results, row ); + entries = g_list_next( entries ); + } + + // TODO: add buttons + + text = g_strdup_printf( _( "We found %i contacts that match your search." ), maxResults ); + + purple_notify_searchresults( session->con, NULL, text, NULL, results, NULL, NULL ); + + g_free( text); +}
--- a/libpurple/protocols/mxit/profile.h Thu Mar 31 04:41:27 2011 +0000 +++ b/libpurple/protocols/mxit/profile.h Thu Mar 31 19:32:34 2011 +0000 @@ -32,26 +32,30 @@ struct MXitProfile { /* required */ char loginname[64]; /* name user uses to log into MXit with (aka 'mxitid') */ - char nickname[64]; /* user's own display name (aka 'nickname', aka 'fullname', aka 'alias') in MXit */ + char userid[51]; /* internal UserId (only in search results) */ + char nickname[101]; /* user's own display name (aka 'nickname', aka 'fullname', aka 'alias') in MXit */ char birthday[16]; /* user's birthday "YYYY-MM-DD" */ gboolean male; /* true if the user's gender is male (otherwise female) */ char pin[16]; /* user's password */ /* optional */ - char title[32]; /* user's title */ - char firstname[64]; /* user's first name */ - char lastname[64]; /* user's last name (aka 'surname') */ - char email[64]; /* user's email address */ + char title[21]; /* user's title */ + char firstname[51]; /* user's first name */ + char lastname[51]; /* user's last name (aka 'surname') */ + char email[201]; /* user's email address */ char mobilenr[21]; /* user's mobile number */ char regcountry[3]; /* user's registered country code */ - gint64 flags; /* user's profile flags */ + char whereami[51]; /* where am I / where I live */ + char aboutme[513]; /* about me */ + + int flags; /* user's profile flags */ gint64 lastonline; /* user's last-online timestamp */ - gboolean hidden; /* set if the user's msisdn should remain hidden */ }; struct MXitSession; void mxit_show_profile( struct MXitSession* session, const char* username, struct MXitProfile* profile ); +void mxit_show_search_results( struct MXitSession* session, int searchType, int maxResults, GList* entries ); gboolean validateDate( const char* bday );
--- a/libpurple/protocols/mxit/protocol.c Thu Mar 31 04:41:27 2011 +0000 +++ b/libpurple/protocols/mxit/protocol.c Thu Mar 31 19:32:34 2011 +0000 @@ -535,28 +535,31 @@ return; } - /* + /* * the mxit server has flood detection and it prevents you from sending messages to fast. * this is a self defense mechanism, a very annoying feature. so the client must ensure that * it does not send messages too fast otherwise mxit will ignore the user for 30 seconds. * this is what we are trying to avoid here.. */ - if ( session->last_tx > ( now - MXIT_TX_DELAY ) ) { - /* we need to wait a little before sending the next packet, so schedule a wakeup call */ - gint64 tdiff = now - ( session->last_tx ); - guint delay = ( MXIT_TX_DELAY - tdiff ) + 9; - if ( delay <= 0 ) - delay = MXIT_TX_DELAY; - purple_timeout_add( delay, mxit_manage_queue_fast, session ); - } - else { - /* get the next packet from the queue to send */ - packet = pop_tx_packet( session ); - if ( packet != NULL ) { - /* there was a packet waiting to be sent to the server, now is the time to do something about it */ - - /* send the packet to MXit server */ - mxit_send_packet( session, packet ); + if ( session->q_fast_timer_id == 0 ) { + /* the fast timer has not been set yet */ + if ( session->last_tx > ( now - MXIT_TX_DELAY ) ) { + /* we need to wait a little before sending the next packet, so schedule a wakeup call */ + gint64 tdiff = now - ( session->last_tx ); + guint delay = ( MXIT_TX_DELAY - tdiff ) + 9; + if ( delay <= 0 ) + delay = MXIT_TX_DELAY; + session->q_fast_timer_id = purple_timeout_add( delay, mxit_manage_queue_fast, session ); + } + else { + /* get the next packet from the queue to send */ + packet = pop_tx_packet( session ); + if ( packet != NULL ) { + /* there was a packet waiting to be sent to the server, now is the time to do something about it */ + + /* send the packet to MXit server */ + mxit_send_packet( session, packet ); + } } } } @@ -587,6 +590,7 @@ { struct MXitSession* session = (struct MXitSession*) user_data; + session->q_fast_timer_id = 0; mxit_manage_queue( session ); /* stop running */ @@ -713,7 +717,7 @@ "ms=%s%c%s%c%i%c%s%c" /* "ms"=password\1version\1maxreplyLen\1name\1 */ "%s%c%i%c%s%c%s%c" /* dateOfBirth\1gender\1location\1capabilities\1 */ "%s%c%i%c%s%c%s" /* dc\1features\1dialingcode\1locale */ - "%c%i%c%i", /* \1protocolVer\1lastRosterUpdate */ + "%c%i%c%i", /* \1protocolVer\1lastRosterUpdate */ session->encpwd, CP_FLD_TERM, clientVersion, CP_FLD_TERM, CP_MAX_FILESIZE, CP_FLD_TERM, profile->nickname, CP_FLD_TERM, profile->birthday, CP_FLD_TERM, ( profile->male ) ? 1 : 0, CP_FLD_TERM, MXIT_DEFAULT_LOC, CP_FLD_TERM, MXIT_CP_CAP, CP_FLD_TERM, session->distcode, CP_FLD_TERM, features, CP_FLD_TERM, session->dialcode, CP_FLD_TERM, locale, @@ -844,7 +848,7 @@ * @param session The MXit session object * @param password The new password to be used for logging in (optional) * @param nr_attrib The number of attributes - * @param attributes String containing the attributes and settings seperated by '0x01' + * @param attributes String containing the attribute-name, attribute-type and value (seperated by '\01') */ void mxit_send_extprofile_update( struct MXitSession* session, const char* password, unsigned int nr_attrib, const char* attributes ) { @@ -853,7 +857,7 @@ int datalen; unsigned int i; - parts = g_strsplit( attributes, "\01", ( MXIT_MAX_ATTRIBS * 3 ) ); + parts = g_strsplit( attributes, "\01", 1 + ( nr_attrib * 3 ) ); /* convert the packet to a byte stream */ datalen = snprintf( data, sizeof( data ), @@ -880,7 +884,7 @@ * @param session The MXit session object * @param max Maximum number of results to return * @param nr_attribs Number of attributes being requested - * @param attribute The names of the attributes + * @param attribute The names of the attributes */ void mxit_send_suggest_friends( struct MXitSession* session, int max, unsigned int nr_attrib, const char* attribute[] ) { @@ -890,8 +894,8 @@ /* convert the packet to a byte stream */ datalen = snprintf( data, sizeof( data ), - "ms=%i%c%s%c%i%c%i", /* inputType \1 input \ 1 maxSuggestions \1 numAttributes \1 name0 ... \1 nameN */ - CP_SUGGEST_FRIENDS, CP_FLD_TERM, "", CP_FLD_TERM, max, CP_FLD_TERM, nr_attrib ); + "ms=%i%c%s%c%i%c%i%c%i", /* inputType \1 input \1 maxSuggestions \1 startIndex \1 numAttributes \1 name0 \1 name1 ... \1 nameN */ + CP_SUGGEST_FRIENDS, CP_FLD_TERM, "", CP_FLD_TERM, max, CP_FLD_TERM, 0, CP_FLD_TERM, nr_attrib ); /* add attributes */ for ( i = 0; i < nr_attrib; i++ ) @@ -919,8 +923,8 @@ /* convert the packet to a byte stream */ datalen = snprintf( data, sizeof( data ), - "ms=%i%c%s%c%i%c%i", /* inputType \1 input \ 1 maxSuggestions \1 numAttributes \1 name0 ... \1 nameN */ - CP_SUGGEST_SEARCH, CP_FLD_TERM, text, CP_FLD_TERM, max, CP_FLD_TERM, nr_attrib ); + "ms=%i%c%s%c%i%c%i%c%i", /* inputType \1 input \1 maxSuggestions \1 startIndex \1 numAttributes \1 name0 \1 name1 ... \1 nameN */ + CP_SUGGEST_SEARCH, CP_FLD_TERM, text, CP_FLD_TERM, max, CP_FLD_TERM, 0, CP_FLD_TERM, nr_attrib ); /* add attributes */ for ( i = 0; i < nr_attrib; i++ ) @@ -987,8 +991,9 @@ * @param username The username of the contact being invited * @param alias Our alias for the contact * @param groupname Group in which contact should be stored. + * @param message Invite message */ -void mxit_send_invite( struct MXitSession* session, const char* username, const char* alias, const char* groupname ) +void mxit_send_invite( struct MXitSession* session, const char* username, const char* alias, const char* groupname, const char* message ) { char data[CP_MAX_PACKET]; int datalen; @@ -997,7 +1002,7 @@ datalen = snprintf( data, sizeof( data ), "ms=%s%c%s%c%s%c%i%c%s", /* "ms"=group\1username\1alias\1type\1msg */ groupname, CP_FLD_TERM, username, CP_FLD_TERM, alias, - CP_FLD_TERM, MXIT_TYPE_MXIT, CP_FLD_TERM, "" + CP_FLD_TERM, MXIT_TYPE_MXIT, CP_FLD_TERM, ( message ? message : "" ) ); /* queue packet for transmission */ @@ -1441,7 +1446,7 @@ const char* statusmsg; const char* profilelist[] = { CP_PROFILE_BIRTHDATE, CP_PROFILE_GENDER, CP_PROFILE_HIDENUMBER, CP_PROFILE_FULLNAME, CP_PROFILE_TITLE, CP_PROFILE_FIRSTNAME, CP_PROFILE_LASTNAME, CP_PROFILE_EMAIL, - CP_PROFILE_MOBILENR, CP_PROFILE_FLAGS }; + CP_PROFILE_MOBILENR, CP_PROFILE_WHEREAMI, CP_PROFILE_ABOUTME, CP_PROFILE_FLAGS }; purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN ); @@ -1850,6 +1855,14 @@ /* last seen online */ profile->lastonline = strtoll( fvalue, NULL, 10 ); } + else if ( strcmp( CP_PROFILE_WHEREAMI, fname ) == 0 ) { + /* where am I */ + g_strlcpy( profile->whereami, fvalue, sizeof( profile->whereami ) ); + } + else if ( strcmp( CP_PROFILE_ABOUTME, fname ) == 0) { + /* about me */ + g_strlcpy( profile->aboutme, fvalue, sizeof( profile->aboutme ) ); + } else { /* invalid profile attribute */ purple_debug_error( MXIT_PLUGIN_ID, "Invalid profile attribute received '%s' \n", fname ); @@ -1869,6 +1882,85 @@ /*------------------------------------------------------------------------ + * Process a received suggest-contacts packet. + * + * @param session The MXit session object + * @param records The packet's data records + * @param rcount The number of data records + */ +static void mxit_parse_cmd_suggestcontacts( struct MXitSession* session, struct record** records, int rcount ) +{ + GList* entries = NULL; + int searchType; + int maxResults; + int count; + int i; + + /* + * searchType \1 numSuggestions \1 total \1 numAttributes \1 name0 \1 name1 \1 ... \1 nameN \0 + * userid \1 contactType \1 value0 \1 value1 ... valueN \0 + * ... + * userid \1 contactType \1 value0 \1 value1 ... valueN + */ + + /* the type of results */ + searchType = atoi( records[0]->fields[0]->data ); + + /* the maximum number of results */ + maxResults = atoi( records[0]->fields[2]->data ); + + /* set the count for attributes */ + count = atoi( records[0]->fields[3]->data ); + + for ( i = 1; i < rcount; i ++ ) { + struct record* rec = records[i]; + struct MXitProfile* profile = g_new0( struct MXitProfile, 1 ); + int j; + + g_strlcpy( profile->userid, rec->fields[0]->data, sizeof( profile->userid ) ); + // TODO: ContactType - User or Service + + for ( j = 0; j < count; j++ ) { + char* fname; + char* fvalue = ""; + + fname = records[0]->fields[4 + j]->data; /* field name */ + if ( records[i]->fcount > ( 2 + j ) ) + fvalue = records[i]->fields[2 + j]->data; /* field value */ + + purple_debug_info( MXIT_PLUGIN_ID, " %s: field='%s' value='%s'\n", profile->userid, fname, fvalue ); + + if ( strcmp( CP_PROFILE_BIRTHDATE, fname ) == 0 ) { + /* birthdate */ + g_strlcpy( profile->birthday, fvalue, sizeof( profile->birthday ) ); + } + else if ( strcmp( CP_PROFILE_GENDER, fname ) == 0 ) { + /* gender */ + profile->male = ( fvalue[0] == '1' ); + } + else if ( strcmp( CP_PROFILE_FULLNAME, fname ) == 0 ) { + /* nickname */ + g_strlcpy( profile->nickname, fvalue, sizeof( profile->nickname ) ); + } + else if ( strcmp( CP_PROFILE_WHEREAMI, fname ) == 0 ) { + /* where am I */ + g_strlcpy( profile->whereami, fvalue, sizeof( profile->whereami ) ); + } + /* ignore other attibutes */ + } + + entries = g_list_append( entries, profile ); + } + + /* display */ + mxit_show_search_results( session, searchType, maxResults, entries ); + + /* cleanup */ + g_list_foreach( entries, (GFunc)g_free, NULL ); +} + + +/*------------------------------------------------------------------------ * Return the length of a multimedia chunk * * @return The actual chunk data length in bytes @@ -2119,6 +2211,11 @@ mxit_parse_cmd_extprofile( session, &packet->records[2], packet->rcount - 2 ); break; + case CP_CMD_SUGGESTCONTACTS : + /* suggest contacts */ + mxit_parse_cmd_suggestcontacts( session, &packet->records[2], packet->rcount - 2 ); + break; + case CP_CMD_MOOD : /* mood update */ case CP_CMD_UPDATE : @@ -2637,9 +2734,13 @@ if ( session->http_timer_id > 0 ) purple_timeout_remove( session->http_timer_id ); - /* remove queue manager timer */ - if ( session->q_timer > 0 ) - purple_timeout_remove( session->q_timer ); + /* remove slow queue manager timer */ + if ( session->q_slow_timer_id > 0 ) + purple_timeout_remove( session->q_slow_timer_id ); + + /* remove fast queue manager timer */ + if ( session->q_fast_timer_id > 0 ) + purple_timeout_remove( session->q_fast_timer_id ); /* remove all groupchat rooms */ while ( session->rooms != NULL ) {
--- a/libpurple/protocols/mxit/protocol.h Thu Mar 31 04:41:27 2011 +0000 +++ b/libpurple/protocols/mxit/protocol.h Thu Mar 31 19:32:34 2011 +0000 @@ -110,7 +110,6 @@ /* Client settings */ #define MAX_QUEUE_SIZE ( 1 << 5 ) /* tx queue size (32 packets) */ #define MXIT_POPUP_WIN_NAME "MXit Notification" /* popup window name */ -#define MXIT_MAX_ATTRIBS 10 /* maximum profile attributes supported */ #define MXIT_DEFAULT_LOCALE "en" /* default locale setting */ #define MXIT_DEFAULT_LOC "planetpurple" /* the default location for registration */ @@ -194,14 +193,19 @@ #define CP_PROFILE_REGCOUNTRY "registeredcountry" /* Registered Country Code (UTF8 String) */ #define CP_PROFILE_FLAGS "flags" /* Profile flags (Bitset) */ #define CP_PROFILE_LASTSEEN "lastseen" /* Last-Online timestamp */ +#define CP_PROFILE_WHEREAMI "whereami" /* Where am I / Where I live */ +#define CP_PROFILE_ABOUTME "aboutme" /* About me */ /* extended profile field types */ -#define CP_PROFILE_TYPE_BOOL 0x02 /* boolean profile attribute type */ -#define CP_PROFILE_TYPE_INT 0x05 /* integer profile attribute type */ -#define CP_PROFILE_TYPE_UTF8 0x0A /* UTF8 string profile attribute type */ -#define CP_PROFILE_TYPE_DATE 0x0B /* date-time profile attribute type */ +#define CP_PROFILE_TYPE_BOOL 0x02 /* boolean (0 or 1) */ +#define CP_PROFILE_TYPE_INT 0x05 /* integer (32-bit) */ +#define CP_PROFILE_TYPE_LONG 0x06 /* long (64-bit) */ +#define CP_PROFILE_TYPE_UTF8 0x0A /* UTF8 string */ +#define CP_PROFILE_TYPE_DATE 0x0B /* date-time (ISO 8601 format) */ /* profile flags */ +#define CP_PROF_NOT_SEARCHABLE 0x02 /* user cannot be searched for */ +#define CP_PROF_NOT_SUGGESTABLE 0x08 /* user cannot be suggested as friend */ #define CP_PROF_DOBLOCKED 0x40 /* date-of-birth cannot be changed */ /* suggestion types */ @@ -305,7 +309,7 @@ void mxit_send_suggest_friends( struct MXitSession* session, int max, unsigned int nr_attrib, const char* attribute[] ); void mxit_send_suggest_search( struct MXitSession* session, int max, const char* text, unsigned int nr_attrib, const char* attribute[] ); -void mxit_send_invite( struct MXitSession* session, const char* username, const char* alias, const char* groupname ); +void mxit_send_invite( struct MXitSession* session, const char* username, const char* alias, const char* groupname, const char* message ); void mxit_send_remove( struct MXitSession* session, const char* username ); void mxit_send_allow_sub( struct MXitSession* session, const char* username, const char* alias ); void mxit_send_deny_sub( struct MXitSession* session, const char* username );
--- a/libpurple/protocols/mxit/roster.c Thu Mar 31 04:41:27 2011 +0000 +++ b/libpurple/protocols/mxit/roster.c Thu Mar 31 19:32:34 2011 +0000 @@ -680,8 +680,9 @@ * @param gc The connection object * @param buddy The new buddy * @param group The group of the new buddy + * @param message The invite message */ -void mxit_add_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group ) +void mxit_add_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group, const char* message ) { struct MXitSession* session = (struct MXitSession*) gc->proto_data; GSList* list = NULL; @@ -702,7 +703,7 @@ * you accept an invite. so in that case the user is already * in our blist and ready to be chatted to. */ - mxit_send_invite( session, buddy_name, buddy_alias, group_name ); + mxit_send_invite( session, buddy_name, buddy_alias, group_name, message ); } else { purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy (scenario 2) (list:%i)\n", g_slist_length( list ) );
--- a/libpurple/protocols/mxit/roster.h Thu Mar 31 04:41:27 2011 +0000 +++ b/libpurple/protocols/mxit/roster.h Thu Mar 31 19:32:34 2011 +0000 @@ -142,7 +142,7 @@ gboolean is_mxit_chatroom_contact( struct MXitSession* session, const char* username ); /* libPurple callbacks */ -void mxit_add_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group ); +void mxit_add_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group, const char* message ); void mxit_remove_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group ); void mxit_buddy_alias( PurpleConnection* gc, const char* who, const char* alias ); void mxit_buddy_group( PurpleConnection* gc, const char* who, const char* old_group, const char* new_group );