diff libpurple/protocols/mxit/roster.c @ 28526:69aa4660401a

Initial addition of the MXit protocol plugin, provided by the MXit folks themselves.
author John Bailey <rekkanoryo@rekkanoryo.org>
date Sun, 08 Nov 2009 23:55:56 +0000
parents
children 5f80ab7ac183
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/roster.c	Sun Nov 08 23:55:56 2009 +0000
@@ -0,0 +1,722 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *			-- user roster management (mxit contacts) --
+ *
+ *				Pieter Loubser	<libpurple@mxit.com>
+ *
+ *			(C) Copyright 2009	MXit Lifestyle (Pty) Ltd.
+ *				<http://www.mxitlifestyle.com>
+ *
+ * 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	<stdio.h>
+#include	<unistd.h>
+#include	<string.h>
+
+#include	"purple.h"
+
+#include	"protocol.h"
+#include	"mxit.h"
+#include	"roster.h"
+
+
+struct contact_invite {
+	struct MXitSession*		session;		/* MXit session object */
+	struct contact*			contact;		/* The contact performing the invite */
+};
+
+
+/*========================================================================================================================
+ * Presence / Status
+ */
+
+/* statuses (reference: libpurple/status.h) */
+static struct status
+{
+	PurpleStatusPrimitive	primative;
+	int						mxit;
+	const char*				id;
+	const char*				name;
+} const mxit_statuses[] = {
+		/*	primative,						no,							id,			name					*/
+		{	PURPLE_STATUS_OFFLINE,			MXIT_PRESENCE_OFFLINE,		"offline",	"Offline"			},	/* 0 */
+		{	PURPLE_STATUS_AVAILABLE,		MXIT_PRESENCE_ONLINE,		"online",	"Available"			},	/* 1 */
+		{	PURPLE_STATUS_AWAY,				MXIT_PRESENCE_AWAY,			"away",		"Away"				},	/* 2 */
+		{	PURPLE_STATUS_AVAILABLE,		MXIT_PRESENCE_AVAILABLE,	"chat",		"Chatty"			},	/* 3 */
+		{	PURPLE_STATUS_UNAVAILABLE,		MXIT_PRESENCE_DND,			"dnd",		"Do Not Disturb"	}	/* 4 */
+};
+
+
+/*------------------------------------------------------------------------
+ * Return list of supported statuses. (see status.h)
+ *
+ *  @param account	The MXit account object
+ *  @return			List of PurpleStatusType
+ */
+GList* mxit_status_types( PurpleAccount* account )
+{
+	GList*				statuslist	= NULL;
+	PurpleStatusType*	type;
+	unsigned int		i;
+
+	for ( i = 0; i < ARRAY_SIZE( mxit_statuses ); i++ ) {
+		const struct status* status = &mxit_statuses[i];
+
+		/* add mxit status (reference: "libpurple/status.h") */
+		type = purple_status_type_new_with_attrs( status->primative, status->id, status->name, TRUE, TRUE, FALSE,
+					"message", _( "Message" ), purple_value_new( PURPLE_TYPE_STRING ),
+					NULL );
+
+		statuslist = g_list_append( statuslist, type );
+	}
+
+	return statuslist;
+}
+
+
+/*------------------------------------------------------------------------
+ * Returns the MXit presence code, given the unique status ID.
+ *
+ *  @param id		The status ID
+ *  @return			The MXit presence code
+ */
+int mxit_convert_presence( const char* id )
+{
+	unsigned int	i;
+
+	for ( i = 0; i < ARRAY_SIZE( mxit_statuses ); i++ ) {
+		if ( strcmp( mxit_statuses[i].id, id ) == 0 )	/* status found! */
+			return mxit_statuses[i].mxit;
+	}
+
+	return -1;
+}
+
+
+/*------------------------------------------------------------------------
+ * Returns the MXit presence as a string, given the MXit presence ID.
+ *
+ *  @param no		The MXit presence I (see above)
+ *  @return			The presence as a text string
+ */
+const char* mxit_convert_presence_to_name( short no )
+{
+	unsigned int	i;
+
+	for ( i = 0; i < ARRAY_SIZE( mxit_statuses ); i++ ) {
+		if ( mxit_statuses[i].mxit == no )				/* status found! */
+			return _( mxit_statuses[i].name );
+	}
+
+	return "";
+}
+
+
+/*========================================================================================================================
+ * Moods
+ */
+
+/*------------------------------------------------------------------------
+ * Returns the MXit mood as a string, given the MXit mood's ID.
+ *
+ *  @param id		The MXit mood ID (see roster.h)
+ *  @return			The mood as a text string
+ */
+const char* mxit_convert_mood_to_name( short id )
+{
+	switch ( id ) {
+		case MXIT_MOOD_ANGRY :
+				return _( "Angry" );
+		case MXIT_MOOD_EXCITED :
+				return _( "Excited" );
+		case MXIT_MOOD_GRUMPY :
+				return _( "Grumpy" );
+		case MXIT_MOOD_HAPPY :
+				return _( "Happy" );
+		case MXIT_MOOD_INLOVE :
+				return _( "In Love" );
+		case MXIT_MOOD_INVINCIBLE :
+				return _( "Invincible" );
+		case MXIT_MOOD_SAD :
+				return _( "Sad" );
+		case MXIT_MOOD_HOT :
+				return _( "Hot" );
+		case MXIT_MOOD_SICK :
+				return _( "Sick" );
+		case MXIT_MOOD_SLEEPY :
+				return _( "Sleepy" );
+		case MXIT_MOOD_NONE :
+		default :
+				return "";
+	}
+}
+
+
+/*========================================================================================================================
+ * Subscription Types
+ */
+ 
+/*------------------------------------------------------------------------
+ * Returns a Contact subscription type as a string.
+ *
+ *  @param subtype	The subscription type
+ *  @return			The subscription type as a text string
+ */
+const char* mxit_convert_subtype_to_name( short subtype )
+{
+	switch ( subtype ) {
+		case MXIT_SUBTYPE_BOTH :
+				return _( "Both" );
+		case MXIT_SUBTYPE_PENDING :
+				return _( "Pending" );
+		case MXIT_SUBTYPE_ASK :
+				return _( "Invited" );
+		case MXIT_SUBTYPE_REJECTED :
+				return _( "Rejected" );
+		case MXIT_SUBTYPE_DELETED :
+				return _( "Deleted" );
+		case MXIT_SUBTYPE_NONE :
+				return _( "None" );
+		default :
+				return "";
+	}
+}
+
+
+/*========================================================================================================================
+ * Calls from the MXit Protocol layer
+ */
+
+#if	0
+/*------------------------------------------------------------------------
+ * Dump a contact's info the the debug console.
+ *
+ *  @param contact		The contact
+ */
+static void dump_contact( struct contact* contact )
+{
+	purple_debug_info( MXIT_PLUGIN_ID, "CONTACT: name='%s', alias='%s', group='%s', type='%i', presence='%i', mood='%i'\n",
+						contact->username, contact->alias, contact->groupname, contact->type, contact->presence, contact->mood );
+}
+#endif
+
+
+#if	0
+/*------------------------------------------------------------------------
+ * Move a buddy from one group to another
+ *
+ * @param buddy		the buddy to move between groups
+ * @param group		the new group to move the buddy to
+ */
+static PurpleBuddy* mxit_update_buddy_group( struct MXitSession* session, PurpleBuddy* buddy, PurpleGroup* group )
+{
+	struct contact*		contact			= NULL;
+	PurpleGroup*		current_group	= purple_buddy_get_group( buddy );
+	PurpleBuddy*		newbuddy		= NULL;
+
+	/* make sure the groups actually differs */
+	if ( strcmp( current_group->name, group->name ) != 0 ) {
+		/* groupnames does not match, so we need to make the update */
+
+		purple_debug_info( MXIT_PLUGIN_ID, "Moving '%s' from group '%s' to '%s'\n", buddy->alias, current_group->name, group->name );
+
+		/*
+		 * XXX: libPurple does not currently provide an API to change or rename the group name
+		 * for a specific buddy. One option is to remove the buddy from the list and re-adding
+		 * him in the new group, but by doing that makes the buddy go offline and then online
+		 * again. This is really not ideal and very iretating, but how else then?
+		 */
+
+		/* create new buddy */
+		newbuddy = purple_buddy_new( session->acc, buddy->name, buddy->alias );
+		newbuddy->proto_data = buddy->proto_data;
+		buddy->proto_data = NULL;
+
+		/* remove the buddy */
+		purple_blist_remove_buddy( buddy );
+
+		/* add buddy */
+		purple_blist_add_buddy( newbuddy, NULL, group, NULL );
+
+		/* now re-instate his presence again */
+		contact = newbuddy->proto_data;
+		if ( contact ) {
+
+			/* update the buddy's status (reference: "libpurple/prpl.h") */
+			if ( contact->statusMsg )
+				purple_prpl_got_user_status( session->acc, newbuddy->name, mxit_statuses[contact->presence].id, "message", contact->statusMsg, NULL );
+			else
+				purple_prpl_got_user_status( session->acc, newbuddy->name, mxit_statuses[contact->presence].id, NULL );
+
+			/* update avatar */
+			if ( contact->avatarId ) {
+				mxit_get_avatar( session, newbuddy->name, contact->avatarId );
+				g_free( contact->avatarId );
+				contact->avatarId = NULL;
+			}
+		}
+
+		return newbuddy;
+	}
+	else
+		return buddy;
+}
+#endif
+
+
+/*------------------------------------------------------------------------
+ * A contact update packet was received from the MXit server, so update the buddy's
+ * information.
+ *
+ *  @param session		The MXit session object
+ *  @param contact		The contact
+ */
+void mxit_update_contact( struct MXitSession* session, struct contact* contact )
+{
+	PurpleBuddy*		buddy	= NULL;
+	PurpleGroup*		group	= NULL;
+	const char*			id		= NULL;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_update_contact: user='%s' alias='%s' group='%s'\n", contact->username, contact->alias, contact->groupname );
+
+	/*
+	 * libPurple requires all contacts to be in a group.
+	 * So if this MXit contact isn't in a group, pretend it is.
+	 */
+	if ( *contact->groupname == '\0' ) {
+		strcpy( contact->groupname, MXIT_DEFAULT_GROUP );
+	}
+
+	/* find or create a group for this contact */
+	group = purple_find_group( contact->groupname );
+	if ( !group )
+		group = purple_group_new( contact->groupname );
+
+	/* see if the buddy is not in the group already */
+	buddy = purple_find_buddy_in_group( session->acc, contact->username, group );
+	if ( !buddy ) {
+		/* buddy not found in the group */
+
+		/* lets try finding him in all groups */
+		buddy = purple_find_buddy( session->acc, contact->username );
+		if ( buddy ) {
+			/* ok, so we found him in another group. to switch him between groups we must delete him and add him again. */
+			purple_blist_remove_buddy( buddy );
+			buddy = NULL;
+		}
+
+		/* create new buddy */
+		buddy = purple_buddy_new( session->acc, contact->username, contact->alias );
+		buddy->proto_data = contact;
+
+		/* add new buddy to list */
+		purple_blist_add_buddy( buddy, NULL, group, NULL );
+	}
+	else {
+		/* buddy was found in the group */
+
+		/* now update the buddy's alias */
+		purple_blist_alias_buddy( buddy, contact->alias );
+
+		/* replace the buddy's contact struct */
+		if ( buddy->proto_data )
+			free( buddy->proto_data );
+		buddy->proto_data = contact;
+	}
+
+	/* load buddy's avatar id */
+	id = purple_buddy_icons_get_checksum_for_user( buddy );
+	if ( id )
+		contact->avatarId = g_strdup( id );
+	else
+		contact->avatarId = NULL;
+
+	/* update the buddy's status (reference: "libpurple/prpl.h") */
+	purple_prpl_got_user_status( session->acc, contact->username, mxit_statuses[contact->presence].id, NULL );
+}
+
+
+/*------------------------------------------------------------------------
+ * A presence update packet was received from the MXit server, so update the buddy's
+ * information.
+ *
+ *  @param session		The MXit session object
+ *  @param username		The contact which presence to update
+ *  @param presence		The new presence state for the contact
+ *  @param mood			The new mood for the contact
+ *  @param customMood	The custom mood identifier
+ *  @param statusMsg	This is the contact's status message
+ *  @param avatarId		This is the contact's avatar id
+ */
+void mxit_update_buddy_presence( struct MXitSession* session, const char* username, short presence, short mood, const char* customMood, const char* statusMsg, const char* avatarId )
+{
+	PurpleBuddy*		buddy	= NULL;
+	struct contact*		contact	= NULL;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_update_buddy_presence: user='%s' presence=%i mood=%i customMood='%s' statusMsg='%s' avatar='%s'\n",
+		username, presence, mood, customMood, statusMsg, avatarId );
+
+	if ( ( presence < MXIT_PRESENCE_OFFLINE ) || ( presence > MXIT_PRESENCE_DND ) ) {
+		purple_debug_info( MXIT_PLUGIN_ID, "mxit_update_buddy_presence: invalid presence state %i\n", presence );
+		return;		/* ignore packet */
+	}
+
+	/* find the buddy information for this contact (reference: "libpurple/blist.h") */
+	buddy = purple_find_buddy( session->acc, username );
+	if ( !buddy ) {
+		purple_debug_warning( MXIT_PLUGIN_ID, "mxit_update_buddy_presence: unable to find the buddy '%s'\n", username );
+		return;
+	}
+
+	contact = buddy->proto_data;
+	if ( !contact )
+		return;
+
+	contact->presence = presence;	
+	contact->mood = mood;
+
+	g_strlcpy( contact->customMood, customMood, sizeof( contact->customMood ) );
+	// TODO: Download custom mood frame.
+
+	/* update status message */
+	if ( contact->statusMsg ) {
+		g_free( contact->statusMsg );
+		contact->statusMsg = NULL;
+	}
+	if ( statusMsg[0] != '\0' )
+		contact->statusMsg = g_strdup( statusMsg );	
+
+	/* update avatarId */
+	if ( ( contact->avatarId ) && ( g_ascii_strcasecmp( contact->avatarId, avatarId ) == 0 ) ) {
+		/*  avatar has not changed - do nothing */
+	}
+	else if ( avatarId[0] != '\0' ) {		/* avatar has changed */
+		if ( contact->avatarId )
+			g_free( contact->avatarId );
+		contact->avatarId = g_strdup( avatarId );
+
+		/* Send request to download new avatar image */
+		mxit_get_avatar( session, username, avatarId );
+	}
+	else		/* clear current avatar */
+		purple_buddy_icons_set_for_user( session->acc, username, NULL, 0, NULL );
+
+	/* update the buddy's status (reference: "libpurple/prpl.h") */
+	if ( contact->statusMsg )
+		purple_prpl_got_user_status( session->acc, username, mxit_statuses[contact->presence].id, "message", contact->statusMsg, NULL );
+	else
+		purple_prpl_got_user_status( session->acc, username, mxit_statuses[contact->presence].id, NULL );
+}
+
+
+/*------------------------------------------------------------------------
+ * update the blist cached by libPurple. We need to do this to keep
+ * libPurple and MXit's rosters in sync with each other.
+ *
+ * @param session		The MXit session object
+ */
+void mxit_update_blist( struct MXitSession* session )
+{
+	PurpleBuddy*	buddy	= NULL;
+	GSList*			list	= NULL;
+	unsigned int	i;
+
+	/* remove all buddies we did not receive a roster update for.
+	 * these contacts must have been removed from another client */
+	list = purple_find_buddies( session->acc, NULL );
+
+	for ( i = 0; i < g_slist_length( list ); i++ ) {
+		buddy = g_slist_nth_data( list, i );
+
+		if ( !buddy->proto_data ) {
+			/* this buddy should be removed, because we did not receive him in our roster update from MXit */
+			purple_debug_info( MXIT_PLUGIN_ID, "Removed 'old' buddy from the blist '%s' (%s)\n", buddy->alias, buddy->name );
+			purple_blist_remove_buddy( buddy );
+		}
+	}
+
+	/* tell the UI to update the blist */
+	purple_blist_add_account( session->acc );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user authorized an invite (subscription request).
+ *
+ *  @param user_data	Object associated with the invite
+ */
+static void mxit_cb_buddy_auth( gpointer user_data )
+{
+	struct contact_invite*	invite	= (struct contact_invite*) user_data;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_buddy_auth '%s'\n", invite->contact->username );
+
+	/* send a allow subscription packet to MXit */
+	mxit_send_allow_sub( invite->session, invite->contact->username, invite->contact->alias );
+
+	/* freeup invite object */
+	if ( invite->contact->msg )
+		g_free( invite->contact->msg );
+	g_free( invite->contact );
+	g_free( invite );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user rejected an invite (subscription request).
+ *
+ *  @param user_data	Object associated with the invite
+ */
+static void mxit_cb_buddy_deny( gpointer user_data )
+{
+	struct contact_invite*	invite	= (struct contact_invite*) user_data;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_buddy_deny '%s'\n", invite->contact->username );
+
+	/* send a deny subscription packet to MXit */
+	mxit_send_deny_sub( invite->session, invite->contact->username );
+
+	/* freeup invite object */
+	if ( invite->contact->msg )
+		g_free( invite->contact->msg );
+	g_free( invite->contact );
+	g_free( invite );
+}
+
+
+/*------------------------------------------------------------------------
+ * A new subscription request packet was received from the MXit server.
+ * Prompt user to accept or reject it.
+ *
+ *  @param session		The MXit session object
+ *  @param contact		The contact performing the invite
+ */
+void mxit_new_subscription( struct MXitSession* session, struct contact* contact )
+{
+	struct contact_invite*	invite;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_new_subscription from '%s' (%s)\n", contact->username, contact->alias );
+
+	invite = g_new0( struct contact_invite, 1 );
+	invite->session = session;
+	invite->contact = contact;
+
+	/* (reference: "libpurple/account.h") */
+	purple_account_request_authorization( session->acc, contact->username, NULL, contact->alias, contact->msg, FALSE, mxit_cb_buddy_auth, mxit_cb_buddy_deny, invite );
+}
+
+
+/*------------------------------------------------------------------------
+ * Return TRUE if this is a MXit Chatroom contact.
+ *
+ *  @param session		The MXit session object
+ *  @param username		The username of the contact
+ */
+gboolean is_mxit_chatroom_contact( struct MXitSession* session, const char* username )
+{
+	PurpleBuddy*		buddy;
+	struct contact*		contact	= NULL;
+
+	/* find the buddy */
+	buddy = purple_find_buddy( session->acc, username );
+	if ( !buddy ) {
+		purple_debug_warning( MXIT_PLUGIN_ID, "is_mxit_chatroom_contact: unable to find the buddy '%s'\n", username );
+		return FALSE;
+	}
+
+	contact = buddy->proto_data;
+	if ( !contact )
+		return FALSE;
+
+	return ( contact->type == MXIT_TYPE_CHATROOM );
+}
+
+
+/*========================================================================================================================
+ * Callbacks from libpurple
+ */
+
+/*------------------------------------------------------------------------
+ * The user has added a buddy to the list, so send an invite request.
+ *
+ *  @param gc		The connection object
+ *  @param buddy	The new buddy
+ *  @param group	The group of the new buddy
+ */
+void mxit_add_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group )
+{
+	struct MXitSession*	session	= (struct MXitSession*) gc->proto_data;
+	GSList*				list	= NULL;
+	PurpleBuddy*		mxbuddy	= NULL;
+	unsigned int		i;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy '%s' (group='%s')\n", buddy->name, group->name );
+
+	list = purple_find_buddies( session->acc, buddy->name );
+	if ( g_slist_length( list ) == 1 ) {
+		purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy (scenario 1) (list:%i)\n", g_slist_length( list ) );
+		/*
+		 * we only send an invite to MXit when the user is not already inside our
+		 * blist.  this is done because purple does an add_buddy() call when
+		 * 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 );
+	}
+	else {
+		purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy (scenario 2) (list:%i)\n", g_slist_length( list ) );
+		/*
+		 * we already have the buddy in our list, so we will only update
+		 * his information here and not send another invite message
+		 */
+
+		/* find the correct buddy */
+		for ( i = 0; i < g_slist_length( list ); i++ ) {
+			mxbuddy = g_slist_nth_data( list, i );
+
+			if ( mxbuddy->proto_data != NULL ) {
+				/* this is our REAL MXit buddy! */
+
+				/* now update the buddy's alias */
+				purple_blist_alias_buddy( mxbuddy, buddy->alias );
+
+				/* now update the buddy's group */
+//				mxbuddy = mxit_update_buddy_group( session, mxbuddy, group );
+
+				/* send the update to the MXit server */
+				mxit_send_update_contact( session, mxbuddy->name, mxbuddy->alias, group->name );
+			}
+		}
+	}
+
+	/*
+	 * we remove the buddy here from the buddy list because the MXit server
+	 * will send us a proper contact update packet if this succeeds.  now
+	 * we do not have to worry about error handling in case of adding an
+	 * invalid contact.  so the user will still see the contact as offline
+	 * until he eventually accepts the invite.
+	 */
+	purple_blist_remove_buddy( buddy );
+
+	g_slist_free( list );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has removed a buddy from the list.
+ *
+ *  @param gc		The connection object
+ *  @param buddy	The buddy being removed
+ *  @param group	The group the buddy was in
+ */
+void mxit_remove_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group )
+{
+	struct MXitSession*	session	= (struct MXitSession*) gc->proto_data;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_remove_buddy '%s'\n", buddy->name );
+
+	mxit_send_remove( session, buddy->name );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user changed the buddy's alias.
+ *
+ *  @param gc		The connection object
+ *  @param who		The username of the buddy
+ *  @param alias	The new alias
+ */
+void mxit_buddy_alias( PurpleConnection* gc, const char* who, const char* alias )
+{
+	struct MXitSession*	session	= (struct MXitSession*) gc->proto_data;
+	PurpleBuddy*		buddy	= NULL;
+	PurpleGroup*		group	= NULL;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_buddy_alias '%s' to '%s\n", who, alias );
+
+	/* find the buddy */
+	buddy = purple_find_buddy( session->acc, who );
+	if ( !buddy ) {
+		purple_debug_warning( MXIT_PLUGIN_ID, "mxit_buddy_alias: unable to find the buddy '%s'\n", who );
+		return;
+	}
+
+	/* find buddy group */
+	group = purple_buddy_get_group( buddy );
+	if ( !group ) {
+		purple_debug_warning( MXIT_PLUGIN_ID, "mxit_buddy_alias: unable to find the group for buddy '%s'\n", who );
+		return;
+	}
+
+	mxit_send_update_contact( session, who, alias, group->name );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user changed the group for a single buddy.
+ *
+ *  @param gc			The connection object
+ *  @param who			The username of the buddy
+ *  @param old_group	The old group's name
+ *  @param new_group	The new group's name
+ */
+void mxit_buddy_group( PurpleConnection* gc, const char* who, const char* old_group, const char* new_group )
+{
+	struct MXitSession*	session	= (struct MXitSession*) gc->proto_data;
+	PurpleBuddy*		buddy	= NULL;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_buddy_group from '%s' to '%s'\n", old_group, new_group );
+
+	/* find the buddy */
+	buddy = purple_find_buddy( session->acc, who );
+	if ( !buddy ) {
+		purple_debug_warning( MXIT_PLUGIN_ID, "mxit_buddy_group: unable to find the buddy '%s'\n", who );
+		return;
+	}
+
+	mxit_send_update_contact( session, who, buddy->alias, new_group );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has selected to rename a group, so update all contacts in that
+ * group.
+ *
+ *  @param gc				The connection object
+ *  @param old_name			The old group name
+ *  @param group			The updated group object
+ *  @param moved_buddies	The buddies affected by the rename
+ */
+void mxit_rename_group( PurpleConnection* gc, const char* old_name, PurpleGroup* group, GList* moved_buddies )
+{
+	struct MXitSession*	session	= (struct MXitSession*) gc->proto_data;
+	PurpleBuddy*		buddy	= NULL;
+	GList*				item	= NULL;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_rename_group from '%s' to '%s\n", old_name, group->name );
+
+	//  TODO: Might be more efficient to use the "rename group" command (cmd=29).
+
+	/* loop through all the contacts in the group and send updates */
+	item = moved_buddies;
+	while ( item ) {
+		buddy = item->data;
+		mxit_send_update_contact( session, buddy->name, buddy->alias, group->name );
+		item = g_list_next( item );
+	}
+}
+