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;
}
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
Today, while I was at work, my cousin stole my apple ipad and tested to see if
it can survive a twenty five foot drop, just so she can be a youtube
sensation. My iPad is now broken and she has 83 views. I know this is entirely off topic but I had to share it with
someone!
This is my first time visit at here and i am in
fact impressed to read all at one place.
I like what you guys are up too. This sort of clever work and reporting!
Keep up the amazing works guys I’ve included you guys to blogroll.
Your mode of telling all in this article is actually good, every one be able to without difficulty know it, Thanks a lot.
Wow, this article is pleasant, my sister is analyzing such things, so I
am going to convey her.
I know this website provides quality depending content and other information, is there any other web page which gives such stuff in quality?
Excellent web site you have got here.. It’s difficult to find high quality writing like yours these days. I really appreciate people like you! Take care!!
http://www.debtconsolidationreviews2013.com
What’s Taking place i am new to this, I stumbled upon this I’ve
found It positively useful and it has helped me out loads.
I hope to give a contribution & assist different customers like its aided me.
Good job.
great issues altogether, you simply received a new
reader. What could you suggest about your put up that you simply
made some days in the past? Any positive?
Fantastic website. Lots of helpful information here. I’m sending it to some buddies ans also sharing in delicious. And of course, thank you in your sweat!
When I initially left a comment I appear to have
clicked on the -Notify me when new comments are added- checkbox and now every time a comment
is added I recieve four emails with the same comment.
There has to be an easy method you can remove me from that service?
Thanks a lot!
Hey! Do you know if they make any plugins to assist with Search Engine
Optimization? I’m trying to get my blog to rank for some targeted keywords but I’m not seeing very good
success. If you know of any please share. Thank you!
I’m not sure exactly why but this site is loading very slow for me. Is anyone else having this problem or is it a issue on my end? I’ll
check back later on and see if the problem still exists.
Hi friends, how is the whole thing, and what you want to say on the topic of this article, in my view
its actually awesome for me.
What’s up to all, the contents present at this site are in fact amazing for people knowledge, well, keep up the nice work fellows.
You really make it seem so easy with your presentation
however I find this matter to be actually one thing which I believe I might by no means understand.
It sort of feels too complex and extremely wide for me.
I am looking ahead to your next submit, I’ll try to get the hang of it!
It is perfect time to make a few plans for the longer term and it’s time to be happy. I have learn this publish and if I could I wish to recommend you few interesting things or suggestions. Maybe you can write next articles relating to this article. I want to learn more issues approximately it!
Hi there, just became aware of your blog through Google,
and found that it is really informative. I am gonna watch out
for brussels. I’ll be grateful if you continue this in future. A lot of people will be benefited from your writing. Cheers!
Hey! I could have sworn I’ve been to this blog before but after reading through some of the post I realized it’s new
to me. Nonetheless, I’m definitely glad I found it and I’ll be book-marking and checking back often!
Wow, awesome weblog structure! How lengthy have you been blogging for?
you make blogging glance easy. The entire look of your web site is magnificent, as
well as the content!
You can definitely see your expertise within the work you write.
The arena hopes for more passionate writers like you who are not afraid to mention how they believe.
All the time follow your heart.