last.fm is a great service. If you haven't heard of it before, it's basically personalized streaming internet radio, with a recommendation, and song/artist/album tagging. One of the nice things about it is that you can stream tagged music. For example, streaming lastfm://globaltags/futurepop would give you a stream made up of all things tagged 'futurepop' by last.fm's users. Subscribers get a further feature -- they can stream their own tags. This is all very nice, but tagging is generally done from within the last.fm player, or from their web interface. What I wanted to do is to script the tagging so that I could tag artists programmatically...
The reason I want to do this is because I'm on an IRC channel with a lot of people who share similar musical taste, and who also use last.fm. So, when someone mentions "Hey, dave0, you would probably like Backlash", it would be nice to have some script within my IRC client tag it as 'recommended' for me, rather than needing to switch desktops to my web browser, search for Backlash, click on "Tag this artist", type my tag, and submit the page.
My first step was to try and figure out the protocol for tagging. last.fm provides some limited API support for accessing information, but it's limited to read-only data about playcounts, favourite artists, recommendations, etc. So, that's a dead end. And, I'd rather not write something to screen-scrape the website, since that's error-prone and will break next time they change their user interface.
The player can tag, though, so let's take a closer look at that. I could have gone to look at the source code of the last.fm player (it's open-source), or at the code for the third-party player I use -- shell-fm -- but I'm a lazy geek, so I pulled out strace. I first found the PID of my shell-fm process, and then ran strace as follows:
# strace -tt -p 15948 -s1000 -eread,write
This traces read and write calls, which I figured (correctly, as it turns out) would be sufficient to see what's going on. Once the strace was running, I initiated tagging of an artist, and watched the output. In one of the write calls, I saw this (cleaned up from strace's output):
POST /1.0/rw/xmlrpc.php HTTP/1.1 Host: ws.audioscrobbler.com User-Agent: Shell.FM 0.2 Content-Length: 595 <?xml version=\"1.0\"?> <methodCall> <methodName>tagArtist</methodName> <params> <param><value><string>dave0</string></value></param> <param><value><string>Shell.FM</string></value></param> <param><value><string>some-hex-data-here</string></value></param> <param><value><string>Absurd Minds</string></value></param> <param><value><array><data><value><string>listen later</string></value></data></array></value></param> <param><value><string>set</string></value></param> </params> </methodCall>
Now we know that tagging is done over HTTP, with XMLRPC. And we have some idea of what data is supposed to go where. (The some-hex-data-here portion is actually my last.fm session key, which I've removed for obvious reasons)
Let's try it. I happen to have XMLRPCsh installed (it comes with SOAP::Lite, available from CPAN, or from Debian as libsoap-lite-perl) which lets you access XMLRPC from the commandline. Mashing those parameters into the correct spots, we get this:
$ XMLRPCsh http://ws.audioscrobbler.com/1.0/rw/xmlrpc.php "tagArtist('dave0','Shell.FM','some-hex-data-here','Backlash',['listen later'],'set')" Usage: method[(parameters)] > --- XMLRPC RESULT --- 'OK'
And, checking the last.fm website shows that yes, it did just tag Backlash with 'listen later'. Great! But, what do those parameters mean?
- The first one is my username
- The second one is the name of the program we're running. Experimentation shows that it's tied to the session identifier hash, so we can't change it without logging in.
- The third one is the session identifier, obtained during login. I'll have to figure out how to log in, as piggybacking off of the shell-fm session isn't going to be reliable.
- The fourth parameter is the name of the artist we're tagging
- The fifth parameter is a list of tags
- The sixth parameter is 'set', which I presume means to set the tag. There may be other options here as well.
Further tracing of the shell-fm process shows tagTrack() and tagAlbum() methods that work similarly, with different parameters in place of the artist name.
Next, I'll need to make this into a scriptable client API that I can plug in to external apps (like my IRC client). More about that in another entry when I get time.