Audio notification in Mcabber

I've already talked about Mcabber in Command Line for Dummies a few days ago. Now one thing that is different in Mcabber from most GUI software is the lack of an obvious feature, and that is audio notification. GUI jabber clients will usually have such a feature, but being a console app, Mcabber offers a far more powerful facility for doing practically any type of notification, and much, much more. Actually, it is so powerful it's ridiculous.

I almost fee guilty for doing mere audio notification with Mcabber, since it can do just about anything, but I suppose I have to start walking before I can run. There are two ways to do (audio) notification. One involves writing a bash script, and another involves writing a Python script. Or a Ruby script. Or PHP... C# anyone? Well, I lied. Mcabber can use anything that can accept arguments. Yes, it will run with Spider Pig, Homer, trust me. So pick your favourite tool, and start hacking. Since I code in Python, and since I've never really dealt with command line arguments, I'll use the sample script that is provided with the Mcabber source tarball.

The binary package in Arch's community repo doesn't come with the contents of the contrib directory that exists in the source tarball, so we'll use ABS to pull in the PKGBUILDs, and download the source code that way.

So, first install ABS if you haven't already during installation of Arch:

# pacman -S abs

Next, pull in the source tree:

# abs

That wasn't that hard, was it? Now we will copy the Mcabber directory from the ABS tree:

$ cp -rv /var/abs/community/network/mcabber ~

To get the source, cd into ~/mcabber and execute the following command:

~/mcabber $ makepkg -o

This downloads and unpacks the source code into ~/mcabber/src. Now create a directory called ~/.mcabber. This is where we'll place our script. Then copy the file ~/mcabber/src/mcabber-0.9.7/contrib/events/mcnotify.py into ~/.mcabber/. Replace 0.9.7 in the souce path with the actual version number of mcabber (0.9.7 as of this writing).

What we will do now is play a .wav file on all incoming messages. We could also filter the messages and sound a beep only when the incoming message has our name in it, or we could even not just beep but format the hard drive, but the choice is up to you. I choose to simply play a .wav on all messages. There aren't too many messages where I live, so it's not a problem. I'll also shop for some free sound effect files from WAVSource.com (it's quite easy to preview sounds there using MPlayer plugin for Firefox, and Firefox, of course).

The reason I'm using Python here is that I wanted to learn a thing or two about the language. Before I started writing this tutorial, I already had a working bash script. But this is more fun. Initially, though, I thought I'd simply beep the PC speaker, but it turned out to be too much of a hassle to be worth my time. So instead of trying to play the beeps using Python, I'll call aplay (ALSA's little player) to play a file in the same directory as the script, which will be called notify.wav.

So let's get started.

Editing mcnotify.py

The sample script is really a very complete one, and ther's not much for us to do there (unless you want something fancy). But we still want to clean it up a bit. Here's the whole script:

#!/usr/bin/python
# Version 0.05
#
#  Copyright (C) 2007 Adam Wolk "Mulander" <netprobe@gmail.com>
#  Slightly updated by Mikael Berthe
#
# To use this script, set the "events_command" option to the path of
# the script (see the mcabberrc.example file for an example)
#
# This script is provided under the terms of the GNU General Public License,
# see the file COPYING in the root mcabber source directory.
#

import sys

#CMD_MSG_IN="/usr/bin/play /home/mulander/sound/machine_move.ogg"
CMD_MSG_IN=""
SHORT_NICK=True

if len(sys.argv) == 5:
	event,arg1,arg2,filename = sys.argv[1:5]
else:
	event,arg1,arg2          = sys.argv[1:4]
	filename                 = None

if event == 'MSG' and arg1 == 'IN':
	import pynotify,os,locale
	encoding  = (locale.getdefaultlocale())[1]
	msg = 'sent you a message.'

	if SHORT_NICK and '@' in arg2:
		arg2 = arg2[0:arg2.index('@')]

	if filename is not None:
		f   = file(filename)
		msg = f.read()

	pynotify.init('mcnotify')
	msgbox = pynotify.Notification(unicode(arg2, encoding),unicode(msg, encoding))
	msgbox.set_timeout(3000)
	msgbox.set_urgency(pynotify.URGENCY_LOW)
	msgbox.show()
	if (CMD_MSG_IN):
		os.system(CMD_MSG_IN + '> /dev/null 2>&1')

	if filename is not None and os.path.exists(filename):
		os.remove(filename)
	pynotify.uninit()

# vim:set noet sts=8 sw=8:

Hacking the example script

Okay, what the original script does is it uses the PyNotify library which, I suspect, simply displays a dialogue on the screen. That's not my idea of audio notifications, so I'm ripping it out. I also don't need to know the system's locale (I mean, I know it, but my script doesn't need to) so that include also isn't necessary. In fact, what we don't need is just about every line from import pynotify,os,locale to the bottom of the file. You can leave the line that says vim:set noet sts=8 sw=8: but I choose to delete that, too.

So, what we're left with after this is:

import sys,os

# the command to execute
CMD_MSG_IN = "aplay -f od %s/.mcabber/notify.wav" % os.path.expanduser("~")

if len(sys.argv) == 5:
	event,arg1,arg2,filename = sys.argv[1:5]
else:
	event,arg1,arg2          = sys.argv[1:4]
	filename                 = None

if event == 'MSG' and (arg1 == 'IN' or arg1 == 'MUC'):

If you look at the code above, you'll notice I've already sneaked in some changes. The first one is I added the CMD_MSG_IN="aplay -f od %s/.mcabber/notify.wav" % os.path.expanduser("~") constant which sets the command that will be executed. The % bits are called string interpolation. It's one of the beauties of Python, but I'll leave it for you to explore it.

Another change is I included arg1 == 'MUC' to make this respond to incoming messages not just those from our buddies, but also from chat rooms (one side effect you'll notice is that this responds even to your own messages when you are participating in a MUC).

Now let's hack this bit by bit. First, let's import subprocess. This enables us to call system commands (and even more, but now we just want to call them).

Then we split the CMD_MSG_IN along spaces: CMD_MSG_IN.split(). You should not that in Python, you always call methods and functions with brackets () even if the brackets are empty. What split() does is simply create a list out of CMD_MSG_IN, which consists of all the parts delimited by spaces. You can also pass an argument to split() and make it slice using some different string. Finally, the string contained in the CMD_MSG_IN is not sliced in place (that is, the output of split() is returned, but not assigned to CMD_MSG_IN). Therefore we'll assign the returned value to a variable called, say, command.

All we have to do now is feed command to subprocess, and we're done. So let's assemble the above and test it.

#!/usr/bin/python
# Version 0.05-sound
#
#  Copyright (C) 2007 Adam Wolk "Mulander" <netprobe@gmail.com>
#  Slightly updated by Mikael Berthe
#  Hacked apart by Branko "Foxbunny" Vukelic <bg.branko@gmail.com>
#
# To use this script, set the "events_command" option to the path of
# the script (see the mcabberrc.example file for an example)
#
# This script is provided under the terms of the GNU General Public License,
# see the file COPYING in the root mcabber source directory.
#
import sys, os

CMD_MSG_IN = "aplay -f cd %s/.mcabber/notify.wav" % os.path.expanduser("~")

if len(sys.argv) == 5:
    event,arg1,arg2,filename = sys.argv[1:5]
else:
    event,arg1,arg2          = sys.argv[1:4]
    filename                 = None

if event == 'MSG' and (arg1 == 'IN' or arg1 == 'MUC'):
    import subprocess
    command = CMD_MSG_IN.split()
    try:
        subprocess.call(command)
    except:
        pass

Time to test this thing. Let's first download a nice sound sample. I don't want the sound to be squeaky or anything high-pitched. Remember, this will be played every time a message of any sort comes in, so in a busy chat room, it's bound to play many times. The file called "Thunk" (sounds like an arrow being fired) from WAVSource.com sounded like a good one, so I saved it as ~/.mcabber/notify.wav.

Next, I made the script executable.

And finally, I changed the part of .mcabberrc file that says set events_command = to read:

set events_command = ~/.mcabber/mcnotify.py

Time to start mcabber and see if it works. Aaaaaand, it works! Hooray!

Conclusion

With this powerful feature, Mcabber can become just about the most powerful jabber client you've ever used. The scripts can be used to handle private messages you send to Mcabber from work, for example. You could, say, start mcabber at home with some JID, then send a message to that JID that Mcabber will write into a file. Or you could send a command to Mcabber to give you system information (like "what's the current uptime?", or something even creative). In other words, you have a complete remote control system sitting right there disguised as an innocent little CLI Jabber client.

Note to Dusty

You once told me that my lame little notes Python script was not good enough for you. And you said Xentac would probably code something with Jabber (or was it IRC) support that he could use remotely. Guess what! I can do that, too, now, with mcabber. ;)

Post new comment

The content of this field is kept private and will not be shown publicly.

Powered by Drupal - Design by artinet