Archive for August, 2010

Capturing IM Events

In the previous post, Writing Your First Instant Messaging Program, we made an IM program that was only capable of capturing four different events. Those four events were when the IM account was connected, an instant message was received from a buddy, the account was disconnected because of an error, or when the machine was disconnected from the internet.

Well, while that’s all fine and dandy, you may want to add more events that your program should listen for, such as when a buddy signs on or off, a buddy’s status changes (and to what status), someone sends you a buddy request, and maybe when a buddy starts (or stops) typing to you. Of course, there are more events that you can track, but these are some basic ones we can start adding to the events we already have. Hopefully, with some examples and some explanation, you will also be able to add more events in the future if need be.

So without further ado, let’s get our hands dirty… hence the picture of dirty hands… which aren’t mine by the way… because if they were, my computer would be slightly dirtier than it already is… ANYWAY…

To start off, here are some things that you should know. For whatever reason, libpurple has two ways of handling what I call “events”. The first is by using what’s called “UI ops“, which stands for “User Interface Operations“. The second is by using what are called “Signals“. What’s the difference between the two? Well, in short, UI ops are much more complicated. For this reason, I will mostly concentrate on signals, but I will introduce UI ops later on as well. In some cases, it may be beneficial to use UI ops instead of signals, but it depends on your requirements.

Signals

Going back to the code in my previous post, let’s jump to the part that contains:

static void signed_on(PurpleConnection *gc)
{

	PurpleAccount *account = purple_connection_get_account(gc);
	printf("Account connected: \"%s\" (%s)\n", purple_account_get_username(account), purple_account_get_protocol_id(account));

}

static void received_im_msg(PurpleAccount *account, char *sender, char *message,
                              PurpleConversation *conv, PurpleMessageFlags flags)
{

	if (conv==NULL)
  	{
  	conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, sender);
  	}

	printf("(%s) %s (%s): %s\n", purple_utf8_strftime("%H:%M:%S", NULL), sender, purple_conversation_get_name(conv), message);

}

static void connect_to_signals(void)
{

	static int handle;

	purple_signal_connect(purple_connections_get_handle(), "signed-on", &handle,
				PURPLE_CALLBACK(signed_on), NULL);

	purple_signal_connect(purple_conversations_get_handle(), "received-im-msg", &handle,
				PURPLE_CALLBACK(received_im_msg), NULL);

}

Lucky for us, signals are pretty straightforward. We can start by looking at connect_to_signals(). In this function, we have set up two signals: one for when the account has signed on, and one for when an IM is received. Looking at each signal, you’ll notice that each signal corresponds to a function. This function is executed whenever the event occurs, and it brings some arguments along with it. Each type of event (or signal) executes a function with a different set of parameters. To figure out exactly what each function should look like, you can take a look here:

http://developer.pidgin.im/doxygen/dev/html/pages.html

After you click on the link for the appropriate type of signal you want, you will see a list of signal names and their functions. In the above example, the signal name goes in the part in blue.

Now that you know how signals work, let’s add a bunch more signals.

static void signed_on(PurpleConnection *gc)
{
	PurpleAccount *account = purple_connection_get_account(gc);
	printf("Account connected: \"%s\" (%s)\n", purple_account_get_username(account), purple_account_get_protocol_id(account));
}

static void buddy_signed_on(PurpleBuddy *buddy)
{
	printf("Buddy \"%s\" (%s) signed on\n", purple_buddy_get_name(buddy), purple_account_get_protocol_id(purple_buddy_get_account(buddy)));
}

static void buddy_signed_off(PurpleBuddy *buddy)
{
	printf("Buddy \"%s\" (%s) signed off\n", purple_buddy_get_name(buddy), purple_account_get_protocol_id(purple_buddy_get_account(buddy)));
}

static void buddy_away(PurpleBuddy *buddy, PurpleStatus *old_status, PurpleStatus *status)
{
	printf("Buddy \"%s\" (%s) changed status to %s\n", purple_buddy_get_name(buddy), purple_account_get_protocol_id(purple_buddy_get_account(buddy)), purple_status_get_id(status));
}

static void buddy_idle(PurpleBuddy *buddy, gboolean old_idle, gboolean idle)
{
	printf("Buddy \"%s\" (%s) changed idle state to %s\n", purple_buddy_get_name(buddy), purple_account_get_protocol_id(purple_buddy_get_account(buddy)), (idle) ? "idle" : "not idle");
}

static void received_im_msg(PurpleAccount *account, char *sender, char *message, PurpleConversation *conv, PurpleMessageFlags flags)
{

	if (conv==NULL)
  	{
  	conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, sender);
  	}

	printf("(%s) %s (%s): %s\n", purple_utf8_strftime("%H:%M:%S", NULL), sender, purple_conversation_get_name(conv), message);

}

static void buddy_typing(PurpleAccount *account, const char *name)
{
	printf("User \"%s\" (%s) is typing...\n", name, purple_account_get_protocol_id(account));
}

static void buddy_typed(PurpleAccount *account, const char *name) //not supported on all protocols
{
	printf("User \"%s\" (%s) has typed something...\n", name, purple_account_get_protocol_id(account));
}

static void buddy_typing_stopped(PurpleAccount *account, const char *name)
{
	printf("User \"%s\" (%s) has stopped typing...\n", name, purple_account_get_protocol_id(account));
}

static int account_authorization_requested(PurpleAccount *account, const char *user)
{
	printf("User \"%s\" (%s) has sent a buddy request\n", user, purple_account_get_protocol_id(account));
	return 1; //authorize buddy request automatically (-1 denies it)
}

static void connect_to_signals(void)
{

	static int handle;

	purple_signal_connect(purple_connections_get_handle(), "signed-on", &handle,
				PURPLE_CALLBACK(signed_on), NULL);

	purple_signal_connect(purple_blist_get_handle(), "buddy-signed-on", &handle,
				PURPLE_CALLBACK(buddy_signed_on), NULL);

	purple_signal_connect(purple_blist_get_handle(), "buddy-signed-off", &handle,
				PURPLE_CALLBACK(buddy_signed_off), NULL);

	purple_signal_connect(purple_blist_get_handle(), "buddy-status-changed", &handle,
				PURPLE_CALLBACK(buddy_away), NULL);

	purple_signal_connect(purple_blist_get_handle(), "buddy-idle-changed", &handle,
				PURPLE_CALLBACK(buddy_idle), NULL);

	purple_signal_connect(purple_conversations_get_handle(), "received-im-msg", &handle,
				PURPLE_CALLBACK(received_im_msg), NULL);

	purple_signal_connect(purple_conversations_get_handle(), "buddy-typing", &handle,
				PURPLE_CALLBACK(buddy_typing), NULL);

	purple_signal_connect(purple_conversations_get_handle(), "buddy-typed", &handle,
				PURPLE_CALLBACK(buddy_typed), NULL);

	purple_signal_connect(purple_conversations_get_handle(), "buddy-typing-stopped", &handle,
				PURPLE_CALLBACK(buddy_typing_stopped), NULL);

	purple_signal_connect(purple_accounts_get_handle(), "account-authorization-requested", &handle,
				PURPLE_CALLBACK(account_authorization_requested), NULL);

}

Hopefully that’s enough to get you started for now. Again, if you go to the link I mentioned earlier, there are many more signals you can use as well.

That’s about all there is to signals. Next, I’ll talk about tracking events using the more complicated UI ops.

UI ops (advanced)

Now, I want to jump to another portion of code from my previous post. Note that some of the functionality of UI ops can be done using signals as well, however, that is not always the case.

static void network_disconnected(void)
{

	printf("This machine has been disconnected from the internet\n");

}

static void report_disconnect_reason(PurpleConnection *gc, PurpleConnectionError reason, const char *text)
{

	PurpleAccount *account = purple_connection_get_account(gc);
	printf("Connection disconnected: \"%s\" (%s)\n  >Error: %d\n  >Reason: %s\n", purple_account_get_username(account), purple_account_get_protocol_id(account), reason, text);

}

static PurpleConnectionUiOps connection_uiops =
{
	NULL,                      /* connect_progress         */
	NULL,                      /* connected                */
	NULL,                      /* disconnected             */
	NULL,                      /* notice                   */
	NULL,                      /* report_disconnect        */
	NULL,                      /* network_connected        */
	network_disconnected,      /* network_disconnected     */
	report_disconnect_reason,  /* report_disconnect_reason */
	NULL,
	NULL,
	NULL
};

static void ui_init(void)
{
	/**
	 * This should initialize the UI components for all the modules.
	 */

	purple_connections_set_ui_ops(&connection_uiops);

}

Looking at this code, instead of reading from top to bottom, read from bottom to top. At the bottom, you’ll notice that first ui_init() calls purple_connections_set_ui_ops(&connection_uiops), where connection_uiops is the list above ui_init(). connection_uiops is just a variable containing a list of functions. The functions in that list are functions that are executed when events occur. By making an element in that list NULL, you are basically ignoring that event. So in this example, we have two functions for two different events. These are network_disconnected and report_disconnect_reason. The first is executed when you are disconnected from the internet. The second is executed when one of your accounts has been disconnected (possibly because of an incorrect password, or the account has signed on somewhere else, etc.). Whatever you put in those functions is entirely up to you. Here, they just print messages.

Now that I’ve given you the technical explanation, let’s look at the same example with one more event added to it.

static void *request_authorize(PurpleAccount *account, const char *remote_user, const char *id, const char *alias, const char *message, gboolean on_list, PurpleAccountRequestAuthorizationCb authorize_cb, PurpleAccountRequestAuthorizationCb deny_cb, void *user_data)
{

	printf("Buddy authorization request from \"%s\" (%s): %s\n", remote_user, purple_account_get_protocol_id(account), message);

	authorize_cb(user_data);
	//deny_cb(user_data);

}

static void network_disconnected(void)
{

	printf("This machine has been disconnected from the internet\n");

}

static void report_disconnect_reason(PurpleConnection *gc, PurpleConnectionError reason, const char *text)
{

	PurpleAccount *account = purple_connection_get_account(gc);
	printf("Connection disconnected: \"%s\" (%s)\n  >Error: %d\n  >Reason: %s\n", purple_account_get_username(account), purple_account_get_protocol_id(account), reason, text);

}

static PurpleAccountUiOps account_uiops =
{
	NULL,                      /* notify_added          */
	NULL,                      /* status_changed        */
	NULL,                      /* request_add           */
	request_authorize,         /* request_authorize     */
	NULL,                      /* close_account_request */
	NULL,
	NULL,
	NULL,
	NULL
};

static PurpleConnectionUiOps connection_uiops =
{
	NULL,                      /* connect_progress         */
	NULL,                      /* connected                */
	NULL,                      /* disconnected             */
	NULL,                      /* notice                   */
	NULL,                      /* report_disconnect        */
	NULL,                      /* network_connected        */
	network_disconnected,      /* network_disconnected     */
	report_disconnect_reason,  /* report_disconnect_reason */
	NULL,
	NULL,
	NULL
};

static void ui_init(void)
{
	/**
	* This should initialize the UI components for all the modules.
	*/

	purple_connections_set_ui_ops(&connection_uiops);
	purple_accounts_set_ui_ops(&account_uiops);

}

Notice anything different? I have now added an event for buddy requests. First, because buddy requests are part of the accounts UI ops, I added purple_accounts_set_ui_ops(&account_uiops) to ui_init(). Then, I created the account_uiops list, containing request_authorize. And finally, I created the request_authorize function at the top. This function will print a message when a buddy request is received and it will automatically authorize the buddy request. You can very easily make it deny the buddy request by switching the uncommented line with the commented one as well.

You may be wondering how I know what the request_authorize function looks like, or how I know where to put the function in the account_uiops list, or how I even knew to call purple_accounts_set_ui_ops(&account_uiops). Unfortunately, this is where Pidgin’s nasty documentation comes in. In general, you can find documentation on UI ops here:

http://developer.pidgin.im/doxygen/dev/html/ui-ops.html

If you click on the relevant UI op and then follow the first link, you will find a list of functions. You will notice that the list of functions is similar to the list of UI ops in the example I used, except many of the functions are set to NULL. Keep in mind that functions in the list of UI ops must be listed in the same order as they are in the documentation, and you must use NULL to fill any empty spaces.

That’s about all there is to capturing events. Hopefully this helps and makes things a little clearer. If you have any questions, please don’t hesitate to ask.

Next up: Important functions in libpurple

purple_signal_connect(purple_blist_get_handle(), “buddy-signed-on”, &handle,
PURPLE_CALLBACK(buddy_signed_on), NULL);

purple_signal_connect(purple_blist_get_handle(), “buddy-signed-off”, &handle,
PURPLE_CALLBACK(buddy_signed_off), NULL);

purple_signal_connect(purple_blist_get_handle(), “buddy-status-changed”, &handle,
PURPLE_CALLBACK(buddy_away), NULL);

purple_signal_connect(purple_blist_get_handle(), “buddy-idle-changed”, &handle,
PURPLE_CALLBACK(buddy_idle), NULL);

purple_signal_connect(purple_blist_get_handle(), “buddy-added”, &handle,
PURPLE_CALLBACK(buddy_added), NULL);

purple_signal_connect(purple_blist_get_handle(), “buddy-removed”, &handle,
PURPLE_CALLBACK(buddy_removed), NULL);

purple_signal_connect(purple_conversations_get_handle(), “received-im-msg”, &handle,
PURPLE_CALLBACK(received_im_msg), NULL);

purple_signal_connect(purple_conversations_get_handle(), “buddy-typing”, &handle,
PURPLE_CALLBACK(buddy_typing), NULL);

purple_signal_connect(purple_conversations_get_handle(), “buddy-typed”, &handle,
PURPLE_CALLBACK(buddy_typed), NULL);

purple_signal_connect(purple_conversations_get_handle(), “buddy-typing-stopped”, &handle,
PURPLE_CALLBACK(buddy_typing_stopped), NULL);

purple_signal_connect(purple_accounts_get_handle(), “account-authorization-denied”, &handle,
PURPLE_CALLBACK(account_authorization_denied), NULL);

purple_signal_connect(purple_accounts_get_handle(), “account-authorization-granted”, &handle,
PURPLE_CALLBACK(account_authorization_granted), NULL);