Tom Insam

I play World of Warcraft. Oh, the shame. But I play it because I'm in a fun guild - we do science!. Well, actually they do science. I'm still at the 'cleaning the glassware afterwards' stage, but a tauren can dream..

Anyway, I code. It's what I do. So once WoLK came out and half the guild went completely insane and started chasing the really silly achievements, it was clear we were going to need an RSS feed of the things. So I built one. It's based on the Armory, like most WoW tools, and is a complete kludge, like most of my tools. But here are my notes anyway.

The trick to scraping the Armoury is pretending to be Firefox. If you visit as a normal web browser, they serve you a traditional HTML page with some Ajax, and it's all quite normal and boring. If you visit the armoury in firefox they return an XML document with an XSL stylesheet referenced in the header that transforms the XML into a web page. Why are they doing this? It must be a huge amount of work compared to just serving HTML, I don't get it. Let's ignore that. Fake a firefox user agent, and you can fetch lovely XML documents that describe things! There's no 'guild achievement' page, alas, so let's start by fetching the page that lists the people in the guild. Using Python.

import urllib, urllib2
opener = urllib2.build_opener()
# Pretend to be firefox
opener.addheaders = [ ('user-agent', 'Mozilla/5.0 (Windows; U; Windows NT 5.0; en-GB; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4') ]
url = "http://eu.wowarmory.com/guild-info.xml?r=%s&n=%s&p=1"%( urllib.quote(realm,''), urllib.quote(guild,'') )
req = urllib2.Request(url)
data = opener.open(req)

(This is the EU armoury, because that's where I am). The armoury is a really unreliable site, so in practice I put lots more error handling round this. But error handling makes for very hard-to-read example code. The XML looks like this:

<page globalSearch="1" lang="en_us" requestUrl="/guild-info.xml">
  <guildKey factionId="1" name="unassigned variable" nameUrl="unassigned+variable" realm="Nordrassil" realmUrl="Nordrassil" url="r=Nordrassil&amp;n=unassigned+variable"/>
  <guildInfo>
    <guild>
      <members filterField="" filterValue="" maxPage="1" memberCount="66" page="1" sortDir="a">
        <character achPoints="2685" class="Hunter" classId="3" gender="Male" genderId="0" level="80" name="Munchausen" race="Tauren" raceId="6" rank="0" url="r=Nordrassil&amp;n=Munchausen"/>
        <character achPoints="1175" class="Paladin" classId="2" gender="Male" genderId="0" level="80" name="Jonadin" race="Blood Elf" raceId="10" rank="1" url="r=Nordrassil&amp;n=Jonadin"/>
        ...

I parse XML using xmltramp, because I'm very lazy and it works. I use xmltramp for all my XML parsing needs. It's old, and there might be something better, but I don't really care. This is a toy.

import xmltramp
xml = xmltramp.seed( data )
toons = xml['guildInfo']['guild']['members']['character':]

That gets us a list of people in the guild. The rendered web page has pagination, but the underlying XML seems to have all characters in a single document, so no messing around fetching multiple pages here. (I've tried this on a guild of 350ish people. Maybe it paginates beyond that. Don't use this script on a guild that big, it won't make you happy.)

Alas, the next thing we have to do is loop over every character and fetch their achievements page (that's why you shouldn't run this script over a large guild). This is extremely unpleasant and slow.

for character in toons:
    char_url = "http://eu.wowarmory.com/character-achievements.xml?r=%s&n=%s"%( urllib.quote(realm,''), urllib.quote(character('name'),'') )
    char_req = urllib2.Request(char_url)
    char_data = opener.open(char_req)
    char_xml = xmltramp.seed( char_data )

The achievement XML looks like this:

...
<achievement categoryId="168" dateCompleted="2009-02-08+01:00" desc="Defeat Shade of Eranikus." icon="inv_misc_coin_07" id="641" points="10" title="Sunken Temple"/>
<achievement categoryId="168" dateCompleted="2009-01-31+01:00" desc="Defeat the bosses in Gundrak." icon="achievement_dungeon_gundrak_normal" id="484" points="10" title="Gundrak"/>
<achievement categoryId="155" dateCompleted="2009-01-31+01:00" desc="Receive a Coin of Ancestry." icon="inv_misc_elvencoins" id="605" points="10" title="A Coin of Ancestry"/>
...

My biggest annoyance here is that there's no timestamp on these things better than 'day', so you don't get very good ordering when you combine them later. I could solve this by storing some state myself, remembering the first time I see each new entry, etc, etc, but I'm trying to avoid keeping any state here, so I don't do that. The XML also lists only 5 achievements per character, and getting more involves fetching a lot more pages, so the final feed includes only the 5 most recent achievements per character. Again, something I could solve with local storage.

Anyway, now I have a list of everyone in the guild, and their last 5 achievements. It's pretty trivial building a list of these and outputting Atom or something. I do it using 'print' statements, myself, because I'm inherently evil. You can't deep-link to the achievement itself on the Armoury, so I link to the wowhead page for individual achievements.

Because the Armoury is unreliable, and my script is slow, I don't use this thing to generate the feed on demand. I have a crontab call the script once an hour, and if it doesn't explode, it copies the result into a directory served by my web browser. If it does explode, then meh, I'll try again in an hour. The feed isn't exactly timely, but we're not controlling nuclear power stations here, we're tracking a computer game. It'll do.

The code I actually run to generate the feed can be found in my repository here, and the resulting feed (assuming you care, which you shouldn't, you're not in the guild..) is here. feel free to steal the code and do your own guild feeds.

Facebook and OpenID


She stumbled upon a solution whereby nearly 99.9% of all test subjects accepted the program, as long as they were given a choice, even if they were only aware of the choice at a near unconscious level. While this answer functioned, it was obviously fundamentally flawed, thus creating the otherwise contradictory systemic anomaly, that if left unchecked might threaten the system itself. Ergo, those that refused the program, while a minority, if unchecked, would constitute an escalating probability of disaster.

The Architect

Oh, sorry. I was going to write something about Facebook making noises about openness. I must have got distracted.

While passing through an airport recently I bought myself a new Nintendo DS Lite, and Ninja Gaiden - Dragon Sword. I'm not going to bother reviewing it - I'm bad at that. But I loved it, in large part because I enjoyed playing the Xbox version ages ago (it was hard) and it plays very similarly.

The whole thing controls with the stylus (and holding any button, which blocks) and it all feels so fluid. It retains the feeling I remember from the xbox, which was that you were always moving somewhere, hitting things, but it never really felt that things weren't under control. I'm impressed that somehow the two games play so similarly, given that the control scheme is so vastly different. All the same things worked - Flying Swallow still works well on certain sorts of enemies, others need proper block/attack synchronisation, collecting essence still works the same, you still have an annoying motivation to find a good group of respawning enemies next to a save point and farm them for money till you can buy all the upgrades... And the camera is a pain, though much less of one than in the Xbox version. It's a fixed viewpoint, and pans, which works well except when you're far enough away that it's hard to judge depth and you're flailing away at things you can't hit.

It's a lot easier and smaller than the first one. It's still linear, but has a hub-and-spoke level design, a central area from which you open progressively more portals. I preferred the Xbox game, which had a long linear-ish progression that looped back on itself, bringing you back to old areas sometimes, but never in a way that made you feel that they were hubs. Well, maybe once. Being given a list of 'here are the portals that you'll be able to open, you've opened these, this one is next' feels like a cheap way of indicating progress through the game. Maybe people like this sort of thing. But the Xbox version felt better as a story telling vehicle.

Anyway, I liked it.

Flame for the iPhone

I've been playing with iPhone development recently, and have ported Flame to it. Well, re-implemented, really - Flame is written in Python and there's no PyObjC for the iPhone, and nor is there likely to ever be. But Objective-C is getting easier as I get practice, and this app even has a modicum of proper memory management.

This time, the source lives in GitHub/jerakeen as git seems like the cool kid this week and I need the practice. I'd expect it to build and run in the simulator just fine, and it runs on my device, so it'll run on yours if you know the magic hoops to jump through. It's possible that this app might actually make it to the App Store at some point, though it's somewhat niche.. You never know.

Let me know if you have ideas for improvements. For a start, I'd like certain services to be linkable - HTTP servers should open their web page in Mobile Safari if clicked, for instance.

On syndication

13:19 <@jerakeen> I have worked out how to syndicate my WOW achievements into my jerakeen.org lifestream.
13:20 <@The> it's becoming a "no life" stream

Trivial. But interesting to me - Addressbook.app no longer cheats with it's startup image - it now uses a static default.png like everyone else. It also seems a lot readier to quit when you run something else, whereas it used to stay resident. This makes me happy. It starts very quickly, and it's nice to pretend there's a level playing field.

Notes and Maps still cheat with their startup images, though.

The iPhone iPod 'podcast' section used to list video podcasts, but only played their audio tracks, which was odd. Now (as of the 2.2 firmware) it'll play video podcasts as well, which is nice. For a start, I can take the 'video' button off the tab bar at the bottom and free up a slot for something more important.

Weirdly, it'll play the video in portrait mode (as well as landscape mode), whereas the video section of the application will still only play them in landscape mode.

Unsubscribe

Unsubscribed from LMN Tactical Newsletter

You really want your template language to automatically escape all strings unless they're flagged as 'I know this contains HTML and I know what I'm doing'. This stops many trivial forms of cross-site-scripting attacks.

You probably also want certain columns of your database to be annotated in such a way that your CMS doesn't accidentally display them to users.