You can think of this as a very short 'howto' on xchat scripting in python, or as a short mostly code-based tutorial. Below is a very simple but non-trivial xchat script written in python. If you know python already, looking at the source should be enough to get a hold for most of the xchat API that you will need to write scripts. If you don't know python, then the below should give you a good idea on how to structure your xchat script and some insights into some of the more popular paradigms in the python programming language. Unfortunately, though, teaching python is outside of the scope of this page and I'll only explain what I feel might not be totally self explanatory.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | #!/usr/bin/env python import sys, re import os import xchat from subprocess import * __module_name__ = "banshee announce" __module_version__ = "0.1" __module_description__ = "banshee announce" def get_output(runstr): return Popen(runstr.split(), stdout=PIPE).communicate()[0] def is_running(): p1 = Popen(["ps", "-ef"], stdout=PIPE) p2 = Popen(["grep", "banshee"], stdin=p1.stdout, stdout=PIPE) output = p2.communicate()[0] if output.rfind("banshee.exe") == -1: return False return True def banshee_title(): return get_output("banshee --hide-field --query-title").strip() def banshee_artist(): return get_output("banshee --hide-field --query-artist").strip() def banshee_position(): return get_output("banshee --hide-field --query-position").strip() def banshee_album(): return get_output("banshee --hide-field --query-album").strip() def banshee_duration(): return get_output("banshee --hide-field --query-duration").strip() def banshee_stop(): get_output("banshee --pause") def banshee_play(): get_output("banshee --play") def banshee_pause(): banshee_stop() def banshee_next(): get_output("banshee --next") def banshee_prev(): get_output("banshee --previous") def banshee_eject(): get_output("banshee --show") class mp3: def __init__(self): self.title = banshee_title() self.artist = banshee_artist() self.album = banshee_album() self.elapsed = int(banshee_position()) self.time = "%2d:%02d" % (self.elapsed/60, self.elapsed % 60) self.ulen = int(banshee_duration()) self.len = "%2d:%02d" % (self.ulen/60, self.ulen % 60) def __str__(self): return "%s - [%s] - %s ~ [%s] of [%s]" % (self.artist, self.album, self.title, self.time, self.len) def announce(): current = mp3() xchat.command('me is listening to: %s' % (current)) def help(): print "\nCommands:" print " \002/mp3\002 : announce the currently playing mp3" print " \002/mp3\002 \00303stop\003 : stop playing" print " \002/mp3\002 \00303play\003 : start playing" print " \002/mp3\002 \00303pause\003 : pause playback" print " \002/mp3\002 \00303next [#]\003 : skip to next (# of) track(s)" print " \002/mp3\002 \00303prev [#]\003 : skip to prev (# of) track(s)" print " \002/mp3\002 \00303open\003 : open files" print "" def xchat_stop(): banshee_stop() def xchat_play(): banshee_play() def xchat_pause(): banshee_pause() def xchat_next(): banshee_next() def xchat_prev(): banshee_pref() def xchat_eject(): banshee_eject() def dispatch(argv, arg_to_eol, c): if len(argv) == 1: announce() return xchat.EAT_XCHAT try: { "help" : help, "stop" : xchat_stop, "play" : xchat_play, "pause" : xchat_pause, "next" : xchat_next, "prev" : xchat_prev, "eject" : xchat_eject, "open" : xchat_eject, }[argv[1]]() except: usage() return xchat.EAT_XCHAT __unhook__ = xchat.hook_command("mp3", dispatch, help="/mp3 help for commands.") |
These lines define global private variables __module_name__, __module_version__, __module_description__. When you look at the scripts window in xchat, your script will appear with this name, version, and description. Xchat actually expects these to be defined, and will error if they aren't. Some of the reloading functionality works off of the 'name', so making the name something simple is probably a good idea.
xchat.hook_command()
is how you set a new 'slash' command in xchat. In this case,
we are setting a new command called 'mp3' which, when used, will call the function dispatch,
and has a help string "/mp3 help for commands." To use this command, a user would type something
like: "/mp3 pause" into xchat.
Callbacks for commands registered with xchat.hook_command
can return different
constants depending on what you want to happen with the command. If you return
xchat.EAT_XCHAT, you are telling xchat that you have already done all of the
processing on this command and you do not want anything further to happen to this event.
This is good for when you are registering your own command, but when you are capturing
events (such as DCC events, say), you will want xchat to carry on handling the event after
you've done your processing.
is_running()
[ lines 15-20 ]The is_running function checks to see if banshee is running. Normally, you would want to filter out the "grep banshee" result, but in this case, since banshee actually runs as "banshee.exe", it isn't important. This function is not used in the code for simplicity, mostly because the banshee --option commands will not choke even if banshee is not running. However, you would probably want to use this to make sure that, if banshee is not running, you do not try to run the announce function.
xchat_*
The purpose of these seemingly useless functions is to provide a mechanism for enhancement in the future. Right now they simply call banshee functions, but in the future this architecture could be easily expanded in order to support multiple players (and in fact, that is what I eventually did.
dispatch(argv, arg_to_eol, c)
[ lines 71-88 ]In most of my xchat modules, I use something similar to this to dispatch command arguments out to different functions. This isolates dispatching errors and command logic errors from eachother, and also lets me use virtually the same code for dispatching across multiple scripts.
Lines 75-85 might seem a little strange to a python beginner (or even python intermediates), but
it is a quasi-popular method of getting switch statement
behavior out of python.
It uses the built in dictionary literal syntax to build a dictionary of function objects, and then
executes an a function with the subsequent arguments based on the command key. In english, suppose
the following is executed by the user:
/mp3 next
This will call the dispatch function with argv = ['mp3', 'next']. If the length of this list
is 1 (ie; we have just called '/mp3'), then we automatically run the announce function. Since the
length in this case would be 2, we go into the try block. argv[1]
in this case is
our "subcommand"; that is, /mp3 takes a number of arguments that themselves are commands, and the
one we are using is 'next'. Since, in this dictionary, the key "next" maps to the function object
xchat_next, {}[argv[1]]
is actually going to be the 'xchat_next' function reference, and
{..}[argv[1]]()
is going to execute that function in response. If the subcommand is
not in the dictionary, we'll get an invalid key exception, and print out the usage.
This is a generally useful organization to dispatch subcommands off of a single hooked command and allows you to cleanly separate the logic of actually carrying out the command and figuring out which one it is the user has requested. Also note that, by passing 'argv', we can let the subcommands parse their own arguments.