i thought i’d make a telepathy plugin so that i could use empathy to talk to some colleagues who don’t have access to an IM client and instead send me many short messages over email.
the telepathy dbus API is documented and there is documentation for telepathy-python but the examples are all on the client side. for a server-side example the docs refer us to the ‘butterfly’ MSN connector (citation required) .. which is quite big.
so, several days of experimentation, reading telepathy-python and butterfly source got me a working email connector and i thought it’d be useful to strip that down even further to an “echo” connector – which just repeats anything you say, like an annoying kid brother, as a reference and example.
what i’ve NOT created is a “reference implementation” – i don’t understand the dbus API docs well enough for that. i think i’ve left out lots of required elements. but it Works™ – at least empathy will let me create an account, start a new conversation and type stuff, which gets repeated. i can disable and re-enable the account without breaking anything – which is nice.
step 1 – advertising the service
the spec says we should have a name for the messaging protocol we’re implementing and a different name for the program which implements it. so i’m calling the protocol “echo” and the program “cave”. the actual script will be called “telepathy-cave” in line with similar programs.
here’s the code for the main program:
#!/usr/bin/python # IMPORTANT! makes asynchronous dbus things work from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) # get the mainloop before creating the ConnectionManager # so that dbus (telepathy) can use it from gobject import MainLoop ml = MainLoop() from cave import caveConnectionManager # get telepathy to start listening for our dbus stuff via the mainloop caveConnectionManager() # and ... Go! ml.run()
the most obvious thing is that it’s running a gobject MainLoop – you wouldn’t want it setting up the Connection Manager and then quitting! the dbus python library uses the mainloop to do it’s event-driven stuff.
that DBusGMainLoop() had me foxed for days. the error message you get if you miss it out:
RuntimeError: To make asynchronous calls, receive signals or export objects, D-Bus connections must be attached to a main loop by passing mainloop=… to the constructor or calling dbus.set_default_main_loop(…)
isn’t all that helpful – since we can’t fix it by “passing mainloop=” or by “calling dbus.set_default_main_loop” because it’s telepathy-python that’s doing that bit. googling for telepathy gets a whole bunch of unhelpful E.S.P. articles! and the butterfly code uses the old way of doing this .. which is Evil™! – it goes like this:
# DON'T DO THIS! import dbus.glib
nope, i haven’t missed anything out there – just importing the module makes a critical something happen. like i said, not good. glad they’ve got rid of that – just need to update butterfly to use the new method! once i’d figured this out i noticed that all the current examples in the documentation do this right – it’s even the very first piece of example code – unfortunately i’d given up looking there because it all seemed client-based.
anyhoo, the last thing that happens before we kick off the MainLoop is we import and create the caveConnectionManager. that’s my telepathy Connection Manager which looks like this (you can save this as cave.py):
#!/usr/bin/python # create a telepathy ConnectionManager for 'cave' import dbus from telepathy.server import ConnectionManager from Constants import PROTOCOL, PROGRAM from Connection import caveConnection class caveConnectionManager(ConnectionManager): def __init__(self): ConnectionManager.__init__(self, PROGRAM) # use telepathy magic to provide required methods self._protos[PROTOCOL] = caveConnection def GetParameters(self, proto): from telepathy import NotImplemented, CONN_MGR_PARAM_FLAG_REQUIRED if proto != PROTOCOL: raise NotImplemented('unknown protocol %s' % proto) return [('account', CONN_MGR_PARAM_FLAG_REQUIRED, 's', '')]
the connection manager sets itself up on the dbus session bus letting the world know that it can create telepathy connections to handle a certain set of protocols – in this case, only “echo”. when a client requests (over dbus) a ‘echo’ connection the connection manager will create a new Connection instance to handle the request.
you can see that the telepathy module has done plenty of the work for us – there’s no code here for talking to dbus or handling client requests – we just need to tell it our name and what protocol we’re providing and what class does the actual work. it’s even set up the GetParameters dbus method for us – we just override it to provide the correct response.
to complete the picture we need a couple more files – Constants.py and Connection.py. Constants is nice and easy:
#!/usr/bin/python PROTOCOL = 'echo' PROGRAM = 'cave'
i just put them there so they didn’t get duplicated across files. Connection.py will get a bit more involved later on but here’s what it looks like for now while it’s not doing anything at all:
#!/usr/bin/python from telepathy.server import Connection from Constants import PROTOCOL, PROGRAM class caveConnection(Connection): def __init__(self, manager, parameters): # assume we have an 'account' name passed to us Connection.__init__(self, PROTOCOL, parameters['account'], PROGRAM)
so, much like the Connection Manager, we let the telepathy module do the work, implementing the basis of the Connection interface API for us.
if you save that lot and run telepathy-cave then you should see “echo” in the list of protocols available in empathy’s “add new account” interface and you should be able to create a new account using it.
next up, creating a channel and sending messages on it!