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

    • Derek
    • July 14th, 2010

    I am SO glad you are doing this! I’ve searched all over for information like this and it simply doesn’t exist anywhere but here. This is an enormous help. I hope you continue the series of tutorials.

    As of right now, I’m having some issues with security, I guess. I tried using my AIM account and got the error:

    libpurple initialized. Running version 2.6.6.
    Connection disconnected: “screen-name-here” (prpl-aim)
    >Error: 0
    >Reason: Error requesting https://api.screenname.aol.com/auth/clientLogin: Unable to connect to api.screenname.aol.com: SSL Connection Failed

    Then I tried Facebook (Bonjour) and got the error:

    Connection disconnected: “user-name-here@chat.facebook.com” (prpl-jabber)
    >Error: 5
    >Reason: You require encryption, but it is not available on this server.

    What should I do about this? Am I missing a library or something?

    Thanks!

  1. @Derek For the first problem, there could be a lot of reasons for this, but I have a feeling that you need to upgrade your version of libpurple to 2.7.0 because it just so happens that there is a problem connecting to AIM in version 2.6.6 (which I see you are using). Depending on your operating system distribution and version, you can get 2.7.0 a few different ways. If you need help with this, let me know what kind of system you are working on and I would be happy to help.

    As for the second problem, I wasn’t even aware that Facebook had a jabber service until now :) I just tried it, and it seems to work for me, but your error makes me think that there is a possible firewall issue either on your system or your network, which may also be causing the first issue. What kind of environment are you working in, and what does your setup look like? Any routers involved? Worst case scenario, there is a dedicated plugin for Facebook chat, which I would have recommended if Facebook didn’t have a jabber service, but now that I see they do, I’d say it is worth it to figure out what’s wrong here first, since the plugin is more of a dirty hack.

    • Derek
    • July 16th, 2010

    Thanks for your prompt response. I upgraded to libpurple 2.7.1 and that fixed the AIM problem!

    The Facebook problem isn’t a network issue because I tried it out in Pidgin and I was able to connect just fine, but I did have to uncheck the box “Require SSL/TLS”, which was selected by default in the Pidgin UI. Could that be the problem?

  2. @Derek I had that thought as well…that it could be a problem with the Require SSL/TLS option. And since that is basically what the error is saying, it sounds like that is most likely the problem. The only thing that I’m confused about is why I didn’t have a problem with it too. Are you using the same exact code that I have posted here? In particular, any changes to this part?:

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

    • Derek
    • July 17th, 2010

    Nope, no changes to anything except the username, password, and prpl-jabber.

  3. @Derek Ok, I have the solution for you…
    After this line near the bottom:

    purple_account_set_password(account, “PASSWORD_HERE”);

    Insert this line:

    purple_account_set_bool(account, “require_tls”, FALSE);

    So you end up with something like:

    PurpleAccount *account = purple_account_new(“USERNAME@chat.facebook.com”, “prpl-jabber”);
    purple_account_set_password(account, “PASSWORD”);

    purple_account_set_bool(account, “require_tls”, FALSE);

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

    That should fix it :)

    • Grant
    • July 27th, 2010

    Wow, I’m glad I found this website.

    Thanks a lot for sharing this information; it’s a shame libpurple doesn’t have proper docs, but hopefully I can figure it out myself with some help from this website!

    • Gatuno
    • July 27th, 2010

    Hi, I was also looking for this information.

    As first look, I don’t understand too much of Glib’s Main Loop. I know how to program in gtk, and I want to see an example using the gtk main loop.

    • Corey
    • August 5th, 2010

    Hey this is great information! Can you please let us know when the next update is coming out. I would like to figure out how to add more features to this sample instant messaging client you’ve created! thanks!

  4. Thanks for all the positive comments guys. As a matter of fact, I am working on the next tutorial right now. I hope to get this out very soon.

  5. The next tutorial is now up.

  6. It’s actually a nice and useful piece of info. I am glad that you shared this helpful info with us. Please keep us informed like this. Thanks for sharing.

    • shorrey
    • April 5th, 2011

    thanks a lot!

    • trustnoone
    • June 30th, 2011

    Hi loving the tutorial especially for a complete novice like me, it helps so much, I did have a problem using it though saying

    /usr/include/glib-2.0/glib/gtypes.h:34:24: fatal error: glibconfig.h: No such file or directory

    in which i had to change “–libs” to “–cflags” for compiling, in any case i hope you make many more tutorials because they are so awesome!!

  7. I’ve searched all over for information like this and it simply doesn’t exist anywhere but here. This is an enormous help. I hope you continue the series of tutorials.

    • marshal
    • August 19th, 2011
  8. Hello,

    I’m trying to make a client on Windows using libpurple. The compilation of hello.c works fine. But the program can’t connect to the server.

    http://www.pidgin-im.de/showthread.php?tid=1397

    Can anybody show me how to use libpurple on Windows?

    Thank you for your help.

  9. An interesting discussion is worth comment. I think that you should write more on this topic, it might not be a taboo subject but generally people are not enough to speak on such topics. To the next. Cheers…

  10. There are some interesting points in time in this article but I donft know if I see all of them center to heart. There is some validity but I will take hold opinion until I look into it further. Good article , thanks and we want more! Added to FeedBur…

  11. Thanks a lot for sharing this information; it’s a shame libpurple doesn’t have proper docs, but hopefully I can figure it out myself with some help from this website

*