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);

    • Corey Foote
    • August 6th, 2010

    Thanks for the update! This is great information. I’m really glad you’re doing this. I would like to find out how to use Libpurple to actually send an instant message. If I could do that, this chat program would be almost complete. Thanks again!

    • RichardRoe
    • August 8th, 2010

    Thanks for the new post! It would be great if tell more about buddy list management, such as adding/removing buddies, sending authorization requests and stuff like this. Also status management lessons will be useful too. Thanks in advance!

    • RichardRoe
    • August 9th, 2010

    And one more question. Is there any preferences of using signals, except easy-to-use? Do signals allow me the same opportunities as UiOps? Or signals are emitted on basic events only? I’ve looked through the libpurple’s documentation, but not exactly sure if i understanded all well :) thanks again!

  1. @Corey Foote and @RichardRoe I plan on writing about sending instant messages, adding/removing buddies, sending buddy requests, and changing statuses in the next upcoming tutorials, probably breaking them apart into short tutorials (except I may combine the adding/removing buddies and sending buddy requests ones).

    And to answer your question about signals vs. UI ops, Richard, in my opinion, if you can do something using signals, I would say to use signals instead of UI ops. The real technical difference between signals and UI ops, in practice, is that UI ops usually give you more access to libpurple’s internal data structures than signals do. This can be good sometimes, but it can also be overkill.

    For example, in libpurple, you can receive buddy requests using either signals or UI ops. If you want to automatically authorize or deny the requests right away, the signal will work just fine. However, if you require user input to authorize or deny the request (which isn’t instant), then you must use UI ops. The reason is because the UI op request_authorize function sends you user_data and the authorization callbacks as arguments, which can be stored and executed at a later time. For the account-authorization-requested signal, however, you must return a number to authorize or deny the request immediately because it does not give you access to that internal data for later execution.

    In other cases, a particular event may only occur as either a signal or a UI op, in which case you have no choice. This is true for the network_disconnected UI op, since there is no signal for this event.

    Hopefully that answers your question.

    • RichardRoe
    • August 9th, 2010

    Thanks you so much, Michael, for the answer. Looking forward to read the new articles!

    • tag
    • April 26th, 2011

    Thank you very much!

  2. I created an Objective-C library for the purpose of using AOL Instant Messanger in your applications. I find that Objective-C is a much cleaner language than C, which is reflected by my code. The library provides most of the same features as Libpurple, but is ridiculously easy to implement. There also are not the same compilation concerns as there are for things like Libpurple. You can simply add the source to your Xcode project, and building will (hopefully) work seamlessly.

    Just thought you might find it cool to see what something that serves the same purpose as Libpurple, written in a different language. Check it out at http://www.github.com/unixpickle/LibOrange .

  3. To Corey: Corey, it’s not that hard to actually send the IM message. I did the investigation myself and I have to admit that I wouldn’t be able to make it work without help of this site and there’s the code I’m using for sending messages:

    int send_msg(PurpleAccount *account, const char *name, char *msg)
    {
    PurplePlugin *prpl = NULL;
    PurplePluginProtocolInfo *prpl_info = NULL;
    PurpleConversation *conv;

    prpl = purple_connection_get_prpl(account->gc);
    if (prpl == NULL)
    return -1;

    prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);

    if (prpl_info->send_im)
    prpl_info->send_im(account->gc, name, msg, 0);
    }

    This function could be easily called like:

    send_msg(account, “someUIN”, “Message”)

    The simple “echo server” for ICQ could be done by appending the code above to the received_msg handler, i.e. something like:

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

    Michal

  4. Great blog .Thank you for sharing

  5. Let me inform you a little bit about bonuses from casino. Today casino has multi bonus to last days of the year

    • libpurple question
    • December 27th, 2011

    The signals that use PurpleBuddy dont work for me, there is something that i didnt put on the code o something extra that i need?

  6. I gues it would be useful to inform you some words about cheap auto insurance. Nowadays rate is free taking into count the end of year

  7. iioaina

  8. wurjtq http://paydayloanscash4u.co.uk/ Payday Loans JTdTxv Payday Loans 9449 [url=http://fastcashpaydayloans4u.com/]bad credit Payday loans[/url] =-]

  9. I have noticed that in old digital cameras, specialized sensors help to maintain focus immediately. Brazilian Slimming Coffee Those kind of devices of some video cameras difference in in the area of contrast, while others make use of a beam associated with infra-red (IR) mild, specifically in minimal super slim light. Higher standards camcorders oftentimes utilize a mixture of equally methods along with may have Encounter Goal Auto focus where the photographic camera can ‘See’ your encounter while focusing merely on that. Many thanks for discussing pai you guo slim capsuleyour opinions with this blog. http://www.lingzhi-2daydiet.com/2day/meizitang.html

  10. Some moccasins are very traditional and resemble the original Native Tods Mens Ankle Boots and others are much more formal and sometimes called Tods Mens Suede Loafers or Tods Mens Leather Moccasins.

*