Marco's Blog

All content personal opinions or work.
en eo

Automate Instant Messages with Pidgin and DBus

2015-04-02 7 min read Howto marco

Despite being an overall fan of KDE, I always preferred the Gnome version of the Instant Messenger, Pidgin. It is really designed for ease of use, it is extensible with incredibly useful plugins, and is available on a ton of platforms. Also, it can be easily configured and you can synchronize the configuration files with no issues, even using OwnCloud or Dropbox.

No surprise then that I would use Pidgin to automate all sorts of tasks. I will send myself a message so I get notified on all my phones, using whatever mechanism I want to use. Pidgin comes with a plethora of protocol plugins. If you need something that isn’t on the list, you can also look for third-party plugins. And you can, of course, write your own. I am doing that as a side project to include small social networking sites that only use a web interface.

One of the advantages of Pidgin is that it is scriptable. You can either write scripts internally (using the plugin mechanism) or you can direct Pidgin from the outside. If you want to call Pidgin methods and make them do things, you use the universal DBus interface.

DBus is universal in that you don’t need a particular environment or programming language to make it work. In fact, DBus was born out of the desire to make different bus interfaces work together. KDE used to have DCOP (which frankly was far superior to DBus). Gnome, if I am not mistaken, used CORBA.

You can send DBus messages using a shell script, or from the command line. DBus comes with utilities that send messages to various interfaces, making it easy to script things.

In my case, I decided to use Python. The Python DBus interface is rock solid and stable, and the language fairly easy to use and parse. If you want to send a message to a DBus object, you simply invoke it.

First, you import that dbus package:

import dbus

Next, you have to specify which bus you want to talk to. On Linux, there are typically two buses started: one for the system (aptly name the systembus) and one for the user’s session (called, you guessed it, sessionbus). The system bus is used for things that the entire computer knows about, like hardware that is attached or general system parameter. The session bus is responsible for everything initiated by a user, like the apps that are running on the desktop.

Since we want to talk to Pidgin, a user-initiated application, we get the session bus:

bus = dbus.SessionBus()

As things go on a computer, now we have to figure out what object we need to talk to:

obj = bus.get_object("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject")

Finally, we get the interface on that object that we are interested in:

purple = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface")

You really can skip the entire setup part logically. You need to do it, but the only thing you’ll really interfact with is the interface. It alone does everything you need to do.

If we want to send a message, we first need to figure out what account to use. Which is where I hit the first snag: there is a method PurpleFindAccount, but it doesn’t seem to work.

Well, where there is a will, there is a way. I couldn’t get purple to find me an account, but I could just walk through the list of accounts. That actually works just fine:

for a in purple.PurpleAccountsGetAllActive():
    if purple.PurpleAccountGetUsername(a) == <a href="mailto:'gazmarco@gmail.com/'">'example_me@gmail.com/'</a>:
        account = a

That leaves the account number in the variable ‘account,’ if there is a match. If there isn’t, then the variable is not initialized.

Assuming we found the matching account, then we can start operating with it. For instance, we can get the account name:

name = purple.PurpleAccountGetUsername(account)

In this case, it would return ‘example_me@gmail.com/’. Interestingly, if you ask PurpleFindAccounts with the account and protocol name returned by the respective calls, it won’t work. Sad Face!

Armed with the account, we can do some damage. We can change the status as we see fit. In my case, I want to send myself a message – typically, the surf conditions to alert me of what’s going on. I love doing that instead of email, because GChat is quite reliable and is better at displaying short messages than GMail, which doesn’t really show much information.

To send a message, we create a new conversation, make it an instant message, and then send a message to that conversation. Something like this:

conv = purple.PurpleConversationNew(1, account, <a href="mailto:'morrismichael44@gmail.com'">'other_me@gmail.com'</a>)
im = purple.PurpleConvIm(conv)
purple.PurpleConvImSend(im, msg)

(Notice how the first time, there was a ‘/’, but not the second time.)

This will automatically send a message from example_me to other_me on GChat. Easy peasy!

Not so fast! Of course I don’t really want to send myself a message when I am at the computer. I want something to run in the background when I am not there and then send me a message when some condition is met. Which is when we hit another snag. You see, the stuff that runs in the background on Linux is started as a cron job. But the cron job runs independently of the session: unless the computer knows how to find the session, the cron job will not be able to connect to it.

Fortunately, it’s fairly easy to get the connection going. Our script just needs to know three things:

  1. the DISPLAY on which the session runs
  2. the XAUTHORITY used to connect to that session
  3. the DBUS_SESSION_BUS_ADDRESS that belongs to that session

All three are identifiers. DISPLAY is typically :0, indicating the first display on the machine. UNIX, of which Linux is an incarnation, is incredibly complicated when it comes to displays. It knows the concept of display, screen, monitor, etc. DISPLAY, though, can be usually assumed to be :0 unless you have a multiuser machine.

The XAUTHORITY is a file used to authenticate the script to the display. Accordingly, it changes whenever the session is restarted. The DBus address works the same way, it is regenerated on every session startup.

There are oodles of ways to get the value of those three variables. An easy one is to simply create a script that runs on startup of the session. The script stores the values of those variables and is automatically reset the next time the session starts. Easy enough.

First, we create a short script that generates the file we need:

#!/bin/bash
file=$HOME/.dbus-connect``touch $file
chmod 600 $file
echo "export DISPLAY=$DISPLAY" > $file
echo "export XAUTHORITY=$XAUTHORITY" >> $file
echo "export DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS" >> $file

I use KDE, so I go to System Settings -> Startup and Shutdown -> Autostart. There, I add a new script (“Add script”) and give it the name of the file we just created. This ensures that the script is run at every startup, so the variables in it will always be fresh. We need to run the script one time manually for the file to be generated before a restart.

Once that is done, we can simply tell cron to read this file and then process the Python script that wants to send a message. That looks like this. First we invoke the crontab editor:

crontab -e

In the file that shows up, we add a line that looks like this:

0 * * * * source ~/.dbus-connect; ~/bin/sendim.py

This would start the script at the top of every hour (the leading zero is the minute). Of course, that means we have a script called ~/bin/sendim.py that is executable and that sends an instant message.

Easy enough, I’d say – once you know the snags you’ll hit.

Register and comment if you have questions!