You start by creating a battle.net account which is easy to get if you already have a Google or Facebook account, but they're not mandatory. If you already play one of their games you already have an account so you can skip the next step.
Create a battle.net account:
- Browse to https://us.battle.net/account/creation/en/
- Enter your account details or use your Google or Facebook account.
Create a Blizzard client:
- Browse to https://develop.battle.net/access/clients
- Logon with your Battle.Net account
- Under Manage Your Clients, click Create New Client
- Enter the client name - I used AuctionHouseAlerts
- Check the "I do not have a service URL for this client" checkbox
- Enter the intended use of the client. I used "Alert me when certain auctions become available"
- Click [Create]
Manage the client:
- Back at https://develop.battle.net/access/clients your client is listed.
- Click on it. Make a note of your client id
- Click [GENERATE NEW SECRET] - this is a temporary password
- Specify how long you want the secret to last. I set mine to 12 months.
- Click [GENERATE]
- Make a note of your secret. The client id and secret are your user id and password for authentication.
Start new WPF, C# project
Call the project AuctionHouseAlerts, target any framework after 4.0 and use C#.
Using NuGet, install Newtonsoft.json.
Step #1 - oAuth2
All calls to the Blizzard APIs require a Token which is obtained from Blizzard using your client id and secret (see Manage the client, above). It's probably possible to use HttpRequest/Response to get the token, but it's easier to use curl. Curl is a command line utility that can be run in a process. It returns json which can be captured from stdout and parsed to extract the token.
Start by making MainWindow.xaml.cs look like this. Make sure you populate ClientID and Secret from your Blizzard client information. Note I am defining class members as strings unless I really need something else - this reduces potential json parsing issues and this isn't a json blog entry.
using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.Windows;
using System.IO;
using System.Net;
using System.Collections.Generic;
using System.Linq;
namespace AuctionHouseAlerts
{
public partial class MainWindow : Window
{
private const string ClientID = "Your Client ID";
private const string Secret = "Your secret";
public class cToken
{
public String access_token;
public String token_type;
public String expires_in;
}
public MainWindow()
{
String Token = GetToken();
InitializeComponent();
}
public String GetToken()
{
String json = "";
String command = string.Format("-u {0}:{1} -d grant_type=client_credentials
https://us.battle.net/oauth/token", ClientID,
Secret);
Process process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "curl",
Arguments = command,
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
process.Start();
while (!process.StandardOutput.EndOfStream)
{
json +=
process.StandardOutput.ReadLine();
}
cToken root =
JsonConvert.DeserializeObject<cToken>(json);
return root.access_token;
}
}
}
Step #2 - Getting Auctions
Before we can get the auction items we need to know what URL to call. We do that by calling another URL (yes, it's a bit confusing).
After the definition of the secret const, add the realm name we are searching on. For example...
private const string Realm = "Icecrown";
In the MainWindow constructor, add the line.
String AuctionURI =
GetAuctionURI(Token);
After the definition of cToken, add the definition of the auction URL.
public class cFile
{
public String url;
public String lastModified;
}
public class cAuctionURL
{
public List<cFile> files;
}
and add the GetAuctionURI method definition in the class somewhere.
public String GetAuctionURI(String token)
{
String URI = string.Format("https://us.api.blizzard.com/wow/auction/data/{0}?locale=en_US&access_token={1}", Realm, token);
HttpWebRequest request =
WebRequest.CreateHttp(URI);
String json;
using (Stream s = request.GetResponse().GetResponseStream())
{
using (StreamReader sr = new StreamReader(s))
{
json = sr.ReadToEnd();
}
}
cAuctionURL root =
JsonConvert.DeserializeObject<cAuctionURL>(json);
return root.files[0].url;
}
If you run to your breakpoint now you will see the auction URL looks a bit like this.
http://auction-api-us.worldofwarcraft.com/auction-data/04167a8875c2972895944a696810c74e/auctions.json
Now we can make the actual call to get the auctions. I only defined getters on the members I plan on binding to.
Add a new class called cAuction.
public class cAuction
{
public String auc;
public String item { get; set; }
public string petName
{ get; set; }
public String owner { get; set; }
public String ownerRealm;
public long bid { get; set; }
public long buyout {
get; set; }
public long quantity
{ get; set; }
public String timeleft { get; set; }
public String rand;
public String seed;
public String context;
public String petSpeciesId { get; set; }
public int
petBreedId { get; set; }
public String petBreed { get; set; }
public String petLevel { get; set; }
public int
petQualityId { get; set; }
public String petQuality { get; set; }
public String gender;
}
public class cRealm
{
public String name;
public string slug;
}
public class cAuctions
{
public List<cRealm> realms { get; set; }
public List<cAuction> auctions { get; set; }
}
In the MainWindow class, add a property that defines a list of cAuction. The name will make more sense later.
public List<cAuction> uniquePetAuctions { get; set; }
Populate uniquePetAuctions in the constructor.
uniquePetAuctions =
GetAuctions(AuctionURI).auctions;
Write the GetAuctions method.
public
cAuctions GetAuctions(String AuctionURI)
{
cAuctions auctions = new cAuctions();
HttpWebRequest request =
WebRequest.CreateHttp(AuctionURI);
String json;
using (Stream s = request.GetResponse().GetResponseStream())
{
using (StreamReader sr = new StreamReader(s))
{
json = sr.ReadToEnd();
}
}
cAuctions root =
JsonConvert.DeserializeObject<cAuctions>(json);
return root;
}
If you run to your breakpoint now you will see that uniquePetAuctions contains a large number of auction details. It's time to bind a datagrid.
Replace MainWindow.XAML with this.
<Window x:Class="AuctionHouseAlert.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace: AuctionHouseAlert
"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="450" Width="800">
<Grid>
<DataGrid ItemsSource="{Binding uniquePetAuctions}" IsReadOnly="True"/>
</Grid>
</Window>
Raw auction house data |
Step #3 - Filtering Battle Pet Auctions
uniquePetAuctions =
GetAuctions(AuctionURI).auctions.FindAll(a => a.item=="82800");
Step #4 - Filtering out Battle Pets our account already has
Add the collected pets class definitions
public class cPetStats
{
public string
speciesId;
public string breedId;
public string
petQualityId;
public string level;
public string health;
public string power;
public string speed;
}
public class cCollectedPet
{
public string name;
public string spellId;
public string
creatureId;
public string itemId;
public string
qualityId;
public string icon;
public cPetStats stats;
public string
battlePetGuid;
public string
isFavorite;
public string
isFirstAbilitySlotSelected;
public string
isSecondAbilitySlotSelected;
public string isThirdAbilitySlotSelected;
public string
creatureName;
public string
canBattle;
}
public class cPetCollection
{
public string
numCollected;
public string
numNotCollected;
public List<cCollectedPet> collected;
}
public class cCharacterPets
{
public string lastModified;
public string name;
public string realm;
public string battlegroup;
public string @class;
public string race;
public string gender;
public string level;
public string achievementPoints;
public string thumbnail;
public string calcClass;
public string faction;
public cPetCollection pets;
}
In the main window constructor add a list of pet ids we already own and call a method to populate it.
List<String>
CollectedPets = GetPetCollection(Token);
public List<String> GetPetCollection(String token)
{
String URI = string.Format("https://us.api.blizzard.com/wow/character/{0}/{1}?locale=en_US&access_token={2}&fields=pets", Realm, Character, token);
HttpWebRequest request =
WebRequest.CreateHttp(URI);
String json;
cCharacterPets cp;
using (Stream s = request.GetResponse().GetResponseStream())
{
using (StreamReader sr = new StreamReader(s))
{
json = sr.ReadToEnd();
}
}
cp =
JsonConvert.DeserializeObject<cCharacterPets>(json);
return cp.pets.collected.Select(p =>
p.stats.speciesId).ToList();
}
uniquePetAuctions =
GetAuctions(AuctionURI).auctions.FindAll(a => a.item=="82800"
&& !CollectedPets.Any(cp => cp == a.petSpeciesId)).OrderBy(a =>
a.buyout).ToList();
Battle pet auctions for pets I don't own - cheapest first |
Step #5 - Populating pet names, replacing IDs with descriptions, and formatting currency
There is a simple call that returns a list of species ids and names which we can use to populate the petName column.
Define cPetName and add a line to the constructor.
public class cPetName
{
public string id;
public string name;
}
public class cPetIndex
{
public List<cPetName> pets;
}
List<cPetName>
PetNames = GetPetNames(Token);
GetPetNames is defined thus.
public List<cPetName> GetPetNames(string token)
{
String URI = string.Format("https://us.api.blizzard.com/data/wow/pet/index?namespace=static-us&locale=en_US&access_token={0}", token);
HttpWebRequest request =
WebRequest.CreateHttp(URI);
String json;
cPetIndex petIndex;
using (Stream s = request.GetResponse().GetResponseStream())
{
using (StreamReader sr = new StreamReader(s))
{
json = sr.ReadToEnd();
}
}
petIndex =
JsonConvert.DeserializeObject<cPetIndex>(json);
return petIndex.pets;
}
We need to replace Quality and Breed Ids with descriptions. Rather than the commonly used B/B format I have chosen to indicate the weights of health, power, and speed explicitly ie 50H/50P/50S. While we're doing this we will take the opportunity to only list the pet with the cheapest buyout for each unique pet/breed.
Replace the contents of the MainWindow constructor with this.
public MainWindow()
{
String Token = GetToken();
String AuctionURI =
GetAuctionURI(Token);
List<String> CollectedPets =
GetPetCollection(Token);
List<cPetName> PetNames =
GetPetNames(Token);
List<cAuction> petAuctions;
List<String> PetQuality = new List<string>() { "Poor", "Common", "Uncommon", "Rare" };
List<String> PetBreed = new List<string>() { "", "", "", "50H/50P/50S", "200P", "200S", "200H", "90H/90P", "90P/90S", "90H/90S", "45H/90P/45S", "45H/45P/90S", "90H/45P/45S" };
petAuctions =
GetAuctions(AuctionURI).auctions.FindAll(a => a.ownerRealm == Realm
&& a.petLevel != null
&& !CollectedPets.Any(cp => cp == a.petSpeciesId)).OrderBy(a =>
a.buyout).ToList();
uniquePetAuctions = new List<cAuction>();
foreach (cAuction auction in petAuctions)
{
if (auction.petBreedId > 12)
{
auction.petBreedId -= 10;
auction.gender = "F";
}
else
auction.gender = "M";
if (!uniquePetAuctions.Any(p => p.petSpeciesId ==
auction.petSpeciesId && p.petBreedId == auction.petBreedId))
{
auction.petName =
PetNames.First(p => p.id == auction.petSpeciesId).name;
auction.petQuality =
PetQuality[auction.petQualityId];
auction.petBreed =
PetBreed[auction.petBreedId];
uniquePetAuctions.Add(auction);
}
}
InitializeComponent();
}
Now we need to convert the bid and buyout values from copper to gold/silver/copper. We will do this with a converter so they will still sort correctly.
Add a new class to the project called Converters and change the code to this...
using System;
using System.Globalization;
using System.Windows.Data;
namespace AuctionHouseAlerts
{
class CurrencyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// coppers to
gsc
long totalCoppers = long.Parse(value.ToString());
long c = totalCoppers % 100;
long totalSilvers = (totalCoppers - c) / 100;
long s = totalSilvers % 100;
long g = (totalSilvers - s) / 100;
string result = g + "g ";
if (c > 0 || s > 0)
{
result += s + "s ";
if (c > 0)
result += c + "c ";
}
return result;
}
public object
ConvertBack(object value,
Type targetType, object
parameter, CultureInfo culture)
{
throw new
NotImplementedException();
}
}
}
<Window x:Class="AuctionHouseAlerts.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:AuctionHouseAlerts"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:CurrencyConverter x:Key="CurrencyConverter"/>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding uniquePetAuctions}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Pet Name" Binding="{Binding petName}"/>
<DataGridTextColumn Header="Owner" Binding="{Binding owner}"/>
<DataGridTextColumn Header="Bid" Binding="{Binding bid, Converter={StaticResource CurrencyConverter}}"/>
<DataGridTextColumn Header="Buyout" Binding="{Binding buyout, Converter={StaticResource CurrencyConverter}}"/>
<DataGridTextColumn Header="Quantity" Binding="{Binding quantity}"/>
<DataGridTextColumn Header="Time Left" Binding="{Binding timeleft}"/>
<DataGridTextColumn Header="Pet Breed" Binding="{Binding petBreed}"/>
<DataGridTextColumn Header="Pet Level" Binding="{Binding petLevel}"/>
<DataGridTextColumn Header="Pet Quality" Binding="{Binding petQuality}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Battle pet auctions that I don't have showing names, quality, breed, and formatted bid and buyouts |