Automated Armory Access Tutorial

2009-01-07 17:52:15

Automated Armory Access Tutorial

Introduction

Welcome to a little tutorial on how to grab data from the Armory and bend it to your will. [insert evil laughter]

Armory Basics

The World of Warcraft Armory is a website that holds information about all characters above level 10 on all of Blizzard's WoW servers.
The Armory was introduced quite a while ago, some time into TBC and first of caused an uproar by people who felt that this information about their character ought to be private and no one shall see it. It paints a decently complete picture of your characters, just some things are missing - but that's good. There's nothing about "amount of gold", "items in inventory and bank", "Recipes knows" and "mailbox system". But you'll find: Class, Race, Level, Guild, Rank, Titles, Equipped Items, Spec, Reputation, Skills, Professions and now after WotLK - also Achievements and Statistics. Some of these could be seen by inspecting before anyway.

What information is available in which file/section is fairly self-explaining. If in doubt, open the URL in a decent browser and inspect the source. It's nice and well-formed XML and they even put some thought on it (well, most of the time, with "Riding skill" you'll tear your hair out).

Armory Structure

This is a typical Armory URL:
http://eu.wowarmory.com/character-statistics.xml?r=vek'lor&n=Armagon&c=130
With these important subparts:
region
(eu, www (for us), kr, zh, ru, ...)
realm
urlencoded (" " = "+")
name
file
character-sheet
character-talents
character-reputation
character-achievements
character-statistics
guild-info
[ character-calendar ]
[ item-info ]
the section isonly important in -statistics and -achievements. Here's a list:
section:
achievements:
92 general
96 quests
97 exploration
95 pvp
168 dungeons & raids
169 professions
201 reputation
155 world events
81 feats of strength
section
statistics:
130 character
141 combat
128 kills
122 deaths
133 quests
132 skills
14807 dungeons & raids
131 social
134 travel
21 pvp

Tools

If you're trying to read data from the Armory be sure to tell your means of communications (socket functions, cURL, etc.) some sensible defaults. With a wrong useragent and language setting you won't get the expected results. "English" and "'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2) Gecko/20070219 Firefox/2.0.0.2" for example works.

My toolset is:

- PHP 5.2 (you need xml stuff, so don't build without libxml, if that's even possible nowadays)
- cURL / peclhttp (better than writing your own socket functions, or abusing fileget_contents())
- [optional] xmllint (part of libxml2-tools or libxml2-dev in debian/ubuntu, more on this later)

Some people published some of their armory-accessing code, you can look at those for reference or use and modify if the licence permits:

- AntiArc's Armory Tools: http://armory.mmo-champion.com/
- ArmoryProfileBot by DarkRyder on WoWWiki: http://www.wowwiki.com/User:ArmoryProfileBot#Source

Getting Started (Short Version)

Well, the basic workflow is this:

- figure out what information you need
- figure out in which part of the armory it's located
- construct the url to query
- query the url and grab the XML
- [ now it gets interesting ]
- [ are you still here? cool. ]
- apply some XPath voodoo to extract the data you need from the xml
- display (and be happy)

Getting Started (Long Version)

We'll be skipping the first 2 steps of the last paragraph and assume you know what you need.
I'll make an example of creating a text-only Armory display for typical use in an IRC Bot.
So it should look like this.

14:00 * Now talking in #c0ders
14:00 < niceguyeddie> 'sup guys, anyone playing WoW?
14:02 <@MrOrange> !char eu vek'lor armagon
14:02 <+Bender> Armagon <Rockthrone>, 25 Undead Warrior
14:05 < niceguyeddie> cool

Shouldn't be too hard. And no, it's not.
To abstract the irc thingy we'll assume the somewhat "dumb" bot just wants a CSV list that reads:

Armagon,Rockthrone,25,Undead,Warrior,
that will be formatted, so he can do a simple

file_get_contents("http://example.org/armory.php?r=vek'lor&n=armagon");

to achieve this (if it's a PHP bot, of course ;))

So, step 1: what do we need?
Just basic data, it's all on character-sheet.xml - very good.
step 2: build the query url:

http://eu.wowarmory.com/character-statistics.xml?r=vek'lor&n=Armagon
(possibly %27 instead of ')
I'm gonna use an example with pecl_http here as it's shorter than with curl:

$useragent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2) Gecko/20070219 Firefox/2.0.0.2';
$query = sprintf("http://%s.wowarmory.com/%s.xml?r=%s&n=%s", $region, $file, $realm, $name);
$response = http_get($query, array('redirect' => 10,'useragent' => $useragent), $info);
$xml = http_parse_message($response)->body;

works like a charm! Well, at least for me. You should get something like

<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="/layout/character-sheet.xsl"?><page globalSearch="1" lang="en_us" requestUrl="/character-sheet.xml">
  <characterInfo>
    <character battleGroup="Schattenbrand" charUrl="r=Vek%27lor&amp;n=Armagon" class="Warrior"
[...]

in $xml, as well

So, now the fun part.
What do we need:

- character name
- guild name
- level
- race
- class

Now starts the XPath Voodoo, which can be annoying at times. There are also other methods, like SimpleXML, so I'm not gonna explain XPath now, it's just what I'm familiar with and it works.
But now comes xmllint into play. if you save all the XML you just got into some "test.xml" and got xmllint handy, you can do this:

$ xmllint --shell test.xml
/ > cd //baseStats/strength
strength > ls @base
ta-        2 53
strength > ls @effective
ta-        2 97

that's displaying this part deep in the tree:

<baseStats>
    <strength attack="174" base="53" block="4" effective="97"/>

But back to our example, what we need is

//character
so we'll do:

$path = '//character';
try {
    $dom = new DOMDocument();
    $dom->loadxml($xml);
    $xpath = new DOMXPath($dom);
} catch (Exception $e) {
}
$nodes = $xpath->query($path);

foreach ($nodes as $no) {
    foreach(array('name', 'guildName', 'level', 'race', 'class') as $key) {
        $result[$key] = $no->getAttribute($key);
    }
}


Hey, nearly finished.
Now we just need to reformat it.
just change it to:

$result = array();
$output = '';
foreach ($nodes as $no) {
    foreach(array('name', 'guildName', 'level', 'race', 'class') as $key) {
        $result[$key] = $no->getAttribute($key);
        $output .= sprintf('%s,', $no->getAttribute($key));
    }
}
echo $output.PHP_EOL;

and indeed it will output:

Armagon,Rockthrone,25,Undead,Warrior,

You could also enclose every item in " ", but there should be no commas in this example items, so I let it out.

Congratulations, you've successfully read from the Armory.

Thanks

- AntiArc for his Armory Tools, I think I would've never brought myself to start playing around if I had to start from scratch. At the start I just hacked up on his source to get things done quick.
- DarkRyder for some more ideas in the ArmoryProfileBot source.
- pcj for having more clearsight than me in a typical ROAR SMASH CANT FIND BUG moment and the suggestion of an alternative title for this document: Web-based Armory Normalization, Keying, Extracting, and Reading

Appendix

Full source code of armory.php

<?php
$region = 'eu';
$file   = 'character-sheet';
$realm  = "vek'lor";
$name   = 'Armagon';

$useragent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2) Gecko/20070219 Firefox/2.0.0.2';
$query = sprintf("http://%s.wowarmory.com/%s.xml?r=%s&n=%s", $region, $file, $realm, $name);
$response = http_get($query, array('redirect' => 10,'useragent' => $useragent), $info);
$xml = http_parse_message($response)->body;


$path = '//character';
try {
    $dom = new DOMDocument();
    $dom->loadxml($xml);
    $xpath = new DOMXPath($dom);
} catch (Exception $e) {
}

$nodes = $xpath->query($path);

$result = array();
$output = '';
foreach ($nodes as $no) {
    foreach(array('name', 'guildName', 'level', 'race', 'class') as $key) {
        $result[$key] = $no->getAttribute($key);
        $output .= sprintf('%s,', $no->getAttribute($key));
    }
}

echo $output.PHP_EOL;
?>

About

Life's a bitch, life's a whore. Nothing less, nothing more.

Read More