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

Writing Your First Instant Messaging Program

Okay, so assuming you have libpurple installed, you can now write your first instant messaging program. This will be written in C. If you’re not too familiar with C, don’t worry. I’ll guide you through it.

First things first, you’ll need to open your terminal. In this case, we’ll be using bash for the shell and nano (same as pico) for the text editor. CD to the directory where you want to run your program and create a file called hello.c (user is your username on this machine):

cd /home/user
nano hello.c

Now, I am going to jump straight into it and give you a fully working libpurple program. I could tell you what every single line does, but instead, I’m just going to show you which parts are necessary and which parts you can play with. The black parts are necessary, the green parts are suggested, and the blue parts are what you probably want to customize:

/*
 * Sample libpurple program written by Michael C. Brook (http://libpurple.com/)
 * (Some fragments taken from libpurple nullclient.c example found at http://pidgin.im/)
 */

#include "purple.h"

#include <glib.h>

#include <signal.h>
#include <string.h>
#include <unistd.h>

#include <stdio.h>

#define CUSTOM_USER_DIRECTORY  "/dev/null"
#define CUSTOM_PLUGIN_PATH     ""
#define PLUGIN_SAVE_PREF       "/purple/user/plugins/saved"
#define UI_ID                  "user"

/**
 * The following eventloop functions are used in both pidgin and purple-text. If your
 * application uses glib mainloop, you can safely use this verbatim.
 */
#define PURPLE_GLIB_READ_COND  (G_IO_IN | G_IO_HUP | G_IO_ERR)
#define PURPLE_GLIB_WRITE_COND (G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL)

typedef struct _PurpleGLibIOClosure {
	PurpleInputFunction function;
	guint result;
	gpointer data;
} PurpleGLibIOClosure;

typedef struct
{
	PurpleAccountRequestType type;
	PurpleAccount *account;
	void *ui_handle;
	char *user;
	gpointer userdata;
	PurpleAccountRequestAuthorizationCb auth_cb;
	PurpleAccountRequestAuthorizationCb deny_cb;
	guint ref;
} PurpleAccountRequestInfo;

static void purple_glib_io_destroy(gpointer data)
{
	g_free(data);
}

static gboolean purple_glib_io_invoke(GIOChannel *source, GIOCondition condition, gpointer data)
{
	PurpleGLibIOClosure *closure = data;
	PurpleInputCondition purple_cond = 0;

	if (condition & PURPLE_GLIB_READ_COND)
		purple_cond |= PURPLE_INPUT_READ;
	if (condition & PURPLE_GLIB_WRITE_COND)
		purple_cond |= PURPLE_INPUT_WRITE;

	closure->function(closure->data, g_io_channel_unix_get_fd(source),
			  purple_cond);

	return TRUE;
}

static guint glib_input_add(gint fd, PurpleInputCondition condition, PurpleInputFunction function,
							   gpointer data)
{
	PurpleGLibIOClosure *closure = g_new0(PurpleGLibIOClosure, 1);
	GIOChannel *channel;
	GIOCondition cond = 0;

	closure->function = function;
	closure->data = data;

	if (condition & PURPLE_INPUT_READ)
		cond |= PURPLE_GLIB_READ_COND;
	if (condition & PURPLE_INPUT_WRITE)
		cond |= PURPLE_GLIB_WRITE_COND;

	channel = g_io_channel_unix_new(fd);
	closure->result = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, cond,
					      purple_glib_io_invoke, closure, purple_glib_io_destroy);

	g_io_channel_unref(channel);
	return closure->result;
}

static PurpleEventLoopUiOps glib_eventloops =
{
	g_timeout_add,
	g_source_remove,
	glib_input_add,
	g_source_remove,
	NULL,
#if GLIB_CHECK_VERSION(2,14,0)
	g_timeout_add_seconds,
#else
	NULL,
#endif

	/* padding */
	NULL,
	NULL,
	NULL
};
/*** End of the eventloop functions. ***/

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

}

static PurpleCoreUiOps core_uiops =
{
	NULL,
	NULL,
	ui_init,
	NULL,

	/* padding */
	NULL,
	NULL,
	NULL,
	NULL
};

static void init_libpurple(void)
{
	/* Set a custom user directory (optional) */
	purple_util_set_user_dir(CUSTOM_USER_DIRECTORY);

	/* We do not want any debugging for now to keep the noise to a minimum. */
	purple_debug_set_enabled(FALSE);

	/* Set the core-uiops, which is used to
	 * 	- initialize the ui specific preferences.
	 * 	- initialize the debug ui.
	 * 	- initialize the ui components for all the modules.
	 * 	- uninitialize the ui components for all the modules when the core terminates.
	 */
	purple_core_set_ui_ops(&core_uiops);

	/* Set the uiops for the eventloop. If your client is glib-based, you can safely
	 * copy this verbatim. */
	purple_eventloop_set_ui_ops(&glib_eventloops);

	/* Set path to search for plugins. The core (libpurple) takes care of loading the
	 * core-plugins, which includes the protocol-plugins. So it is not essential to add
	 * any path here, but it might be desired, especially for ui-specific plugins. */
	purple_plugins_add_search_path(CUSTOM_PLUGIN_PATH);

	/* Now that all the essential stuff has been set, let's try to init the core. It's
	 * necessary to provide a non-NULL name for the current ui to the core. This name
	 * is used by stuff that depends on this ui, for example the ui-specific plugins. */
	if (!purple_core_init(UI_ID)) {
		/* Initializing the core failed. Terminate. */
		fprintf(stderr,
				"libpurple initialization failed. Dumping core.\n"
				"Please report this!\n");
		abort();
	}

	/* Create and load the buddylist. */
	purple_set_blist(purple_blist_new());
	purple_blist_load();

	/* Load the preferences. */
	purple_prefs_load();

	/* Load the desired plugins. The client should save the list of loaded plugins in
	 * the preferences using purple_plugins_save_loaded(PLUGIN_SAVE_PREF) */
	purple_plugins_load_saved(PLUGIN_SAVE_PREF);

	/* Load the pounces. */
	purple_pounces_load();
}

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

}

int main(int argc, char *argv[])
{

	GMainLoop *loop = g_main_loop_new(NULL, FALSE);

	/* libpurple's built-in DNS resolution forks processes to perform
	 * blocking lookups without blocking the main process.  It does not
	 * handle SIGCHLD itself, so if the UI does not you quickly get an army
	 * of zombie subprocesses marching around.
	 */
	signal(SIGCHLD, SIG_IGN);

	init_libpurple();

	printf("libpurple initialized. Running version %s.\n", purple_core_get_version()); //I like to see the version number

	connect_to_signals();

	PurpleAccount *account = purple_account_new("YOUR_IM_ACCOUNTS_USERNAME_HERE", "prpl-IM_NETWORK_HERE"); //this could be prpl-aim, prpl-yahoo, prpl-msn, prpl-icq, etc.
	purple_account_set_password(account, "YOUR_IM_ACCOUNTS_PASSWORD_HERE");

	purple_accounts_add(account);
	purple_account_set_enabled(account, UI_ID, TRUE);

	g_main_loop_run(loop);

	return 0;

}

Download hello.c

I know this might look like one big scrambled mess, but for the time being, let’s not concern ourselves with what the program actually does. First, let’s just see if it works. Copy the above program into the text editor in your terminal. Then, make sure to replace YOUR_IM_ACCOUNTS_USERNAME_HERE, YOUR_IM_ACCOUNTS_PASSWORD_HERE, and IM_NETWORK_HERE with your IM account’s username, password, and network, respectively. Here’s a list of networks that libpurple supports by default. You will need to insert the “prpl-whatever” part in place of the network in your program:

AIM		prpl-aim
Bonjour		prpl-bonjour
Gadu-Gadu	prpl-gg
GroupWise	prpl-novell
ICQ		prpl-icq
IRC		prpl-irc
MSN		prpl-msn
MySpaceIM	prpl-myspace
QQ		prpl-qq
SILC		prpl-silc
SIMPLE		prpl-simple
Sametime	prpl-meanwhile
XMPP		prpl-jabber
Yahoo		prpl-yahoo
Yahoo JAPAN	prpl-yahoojp
Zephyr		prpl-zephyr

After you have edited the necessary parts of the program, save and close it (hit CTRL+X, Y, and ENTER). Now, we need to compile the program. To do so, enter the following command:

gcc `pkg-config --libs glib-2.0` -I /usr/include/libpurple/ -I /usr/include/glib-2.0/ -I /usr/lib/glib-2.0/include/ -lpurple hello.c -o hello

This is where you will find out if everything is working properly or not. If you receive no output after running this command, congratulations! Everything is working fine. However, if you receive any errors, it is likely to be related to your installation of libpurple or glib. If you have already gone through the installation tutorial but you are still getting errors, feel free to leave a comment and we’ll see if we can sort it out.

(Note: the paths used to include libpurple and glib in the command may depend on your operating system and the paths you have chosen to install each library in. This is confirmed to work at least for Ubuntu and Debian Linux using the default installation paths. Also note that using the CC compiler instead of GCC will work as well.)

Now, it is time to run your program! Type in the terminal:

./hello

If all goes well, your account should be signed in through your program, and you should see something similar to:

libpurple initialized. Running version 2.6.2.
Account connected: "username" (prpl-network)

At the time this tutorial was written, version 2.6.2 was the latest stable version. And of course, you will see your own “username” and “prpl-network”. If you would like to exit the program, just hit CTRL+C. If your account was connected, it will be signed off.

For the time being, the program will only print messages when your account is connected, an instant message is received, the IM account is disconnected because of an error, or the machine is disconnected from the internet. These are just examples of events that you can track and perform any action from. You can actually track events anywhere from an account sign-on, to a buddy request being received, to when a buddy is typing to you, to pretty much anything else you can imagine. This is especially useful when you are working with databases and would like to update record values based on different events. You can also send out buddy requests, instant messages, programmatically show a buddy that you are typing to them, and all of the above. In short, you can programmatically do anything your instant messenger can do and more.

If you decide to go back and make any changes to your program, just remember to compile your program again afterwards, since the actual program is running from the compiled version (it is not automatically modified by simply saving the source code). For PHP developers (like me), this is something you need to remember.

Well, I hope you enjoyed the ride! Again, if you have trouble, don’t hesitate to leave a comment.

Next up: Capturing events

How to Install libpurple

Installing libpurple is actually pretty easy. For this example, I will show you how to install libpurple on Ubuntu Linux using apt-get (should work on Debian as well).

In your terminal, type:

sudo apt-get install libpurple-dev

And…well…that’s it! And hopefully, libpurple was installed with no errors. However, if you do have errors, please feel free to leave a comment below.

Just as a side note, libpurple requires Glib to run. Commonly, this comes installed on Linux, however, it may not already be installed on a Windows or Mac machine. Since I have never installed Glib on Windows or Mac, I can’t tell you exactly how to do it, but if you type in “how to install glib on windows/mac” in Google, you should be able to find some helpful resources.

Of course, using apt-get is not the only way to install liburple in Linux (although it’s by far the easiest). You can also get it from the official website, where you can either choose your system type or download the source code. It is possible to install libpurple from the source code, although I highly discourage it unless you plan on modifying libpurple itself.

Roughly, to install from source, you must download the source code, cd to the directory, and do something along the lines of ./configure, make, and make install, which will take a very very long time since it must re-compile all of the source code.

Once you have installed libpurple, your system is now equipped to handle instant messaging programs, which I will explain next.

If you had any trouble installing libpurple or thought this was at all useful, please leave a comment! Thanks.

Next: Writing your first IM program with libpurple

What is libpurple?

Libpurple picture

Okay, so starting things off, what the heck is libpurple??

If you google a definition for libpurple, you’ll get something like, “Pidgin (formerly named Gaim) is a multi-platform instant messaging client, based on a library named libpurple.”

So basically, libpurple is a programming interface for instant messaging over various networks (such as AIM, Yahoo, MSN, ICQ, etc.). Pidgin, on the other hand, is an instant messaging application that uses libpurple and includes a GUI (graphical user interface) for all us normal people out there.

What can you do with libpurple, you ask? Lots of nifty stuff.

Since libpurple is essentially an API, pretty much anything your instant messenger can do, you can do programmatically using libpurple. Want to make your own IM robot? Go ahead! I’ll show you how.

Does it require anything special?

All you need is an installation of libpurple on whatever system it’s going to run on and an IM account on pretty much any network, and that’s about it. (And you don’t need to register your IM account as a bot). Since libpurple is written in C, your programs will have to be written in C as well. If you’re not a pro in C, don’t panic. I never used C a day in my life until I wrote an instant messaging program using libpurple. Fortunately, libpurple is pretty easy to understand once you get the hang of it. Trust me on this one.

Since I am mostly a web developer, I am going to show you how to create and run IM programs on a Linux server over the command line. If you are a light web developer with your own website, but you don’t have access to the command line on your server (say, if you have a shared web server…), you’re going to need either a virtual private server or a dedicated server OR if you have just any old computer laying around, you can use that too! All you need is administrative access on that computer/server.

Who uses libpurple?

Probably one of the most notable applications of libpurple on the web is meebo.com. They provide a web-based instant messaging service for several different networks, including Facebook Chat (yes, you can do Facbeook Chat too). For a full list of who uses libpurple, you can go here.

Who created libpurple?

Not me! Sorry to disappoint you. It was actually made by these guys http://pidgin.im/. I just use libpurple for real world applications over the web. I’m just a normal guy, not a pro in libpurple, but I have learned many things the hard way because of such little documentation on this. If you go searching for tutorials on how to use libpurple, this is about the best you’ll find:

Your application will first initialize the core (purple_core_init), add plugin-search paths, load the saved plugins, prefs etc. Your best bet is to check out a copy of the source code and look at finch/finch.c:init_libpurple().

(stright from the Pidgin website)

As you can see, not very helpful…so that’s why I’m here, to contribute what I wish I had a looong time ago.

Next up: Tutorials!

It’s alive!

I am proud to announce the launch of libpurple.com today (May 18, 2010)! What are we going to do, you ask? Well if you must know…

If you haven’t noticed already, there are very little resources out on the web to help new developers program with libpurple (which by the way is an instant messaging C library, for those of you who don’t know). So with such little help, what do you do? This is where we would like to come in.

As an experienced developer in libpurple (and many web programming languages and databases), I would like to share with everyone what I know about libpurple and how to use it in real applications. I plan on providing tutorials and how-to’s written for normal people in layman’s terms so that everyone and anyone could understand it. Also, with our blog-like interface, I hope to get some potential contributions from other users as well.

In short, we want to be your #1 resource for instant message application programming tutorials and how-to’s!

By the way, I like to say “we” a lot, but this is really a one manned job. I just think “I” sounds too conceited :p

-Michael C. Brook