Blog-style website for karathan using the self-made Stellar Theme powered by HTML5Up http://blog.karathan.at

wow-auction-tracking.md 7.4KB


Title: “Blizzard API: Tracking WoW Auctions” Template: article Description: Spiele- Entwickler und Publisher Blizzard Entertainment stellt mittlerweile die umfangreiche Battle.net API zur Verfügung. Über sie kann mittels REST ein Teil der Spieldatenbank, sowie statische Information, gelesen werden. In diesem Artikel stelle ich euch ein kleines Projekt vor, den AuctionTracker, welches meine Auktionen überwachen und mich mittels Push Notification über Updates informieren kann.

ReleaseIndex: 1

World of Warcraft besitzt mit seinem Umfangreichen Auktionshaus eine Handelsplattform für alle Goblins von Azeroth, auch jenen, die unter dem blauen Banner der Allianz Handel betreiben. Seit einiger Zeit bietet die Battle.net API auch eine REST Schnittstelle für uns Goblins, man kann, zumindest mit einiger Zeitverzögerung, das Auktionshaus seines Realms auslesen, und so einen Überblick bekommen, welche Auktionen gerade eingestellt sind. Eine Idee begann mir im Kopf herum zu schwirren: Wieso nicht einfach die eigenen Auktionen auslesen, filtern und mit diesen Rohdaten eventuell Visualisierungen, oder Analysen des eigenen Marktverhaltens zu erstellen, so zumindest der erste Gedanke. Weiter dachte ich mir, es sei von Vorteil, seine Auktionen auch außerhalb des Spiels verfolgen zu können, und v.a. die Information über abgelaufene Items zu erhalten, sodass man diese im Zweifel schnell wieder in den Markt bringen kann.

AuctionTracker

Der AuchtionTracker tut genau das, er analysiert die Auktionsdatenbank, und sendet alle abgelaufenen, sowie alle verkauften Auktionen an einen Push Server, welcher Nachrichten an meine Endgeräte verteilt. Im Grunde synchronisiert der AuctionTracker seine interne Datenbank mit der Blizzard Auction DB, und erzeugt so zu je zwei Zeitpunkten einen Snapshot. Wenn eine Auktion fehlt, ist diese entweder abgelaufen, oder verkauft worden. Was genau passiert ist, ermittelt das Tool aufgrund der letzten Verfügbaren Zeitspanne der respektiven Auktion. Ist diese SHORT, also hat eine Lebensdauer von 30 Minuten oder weniger, so geht das Tool davon aus, dass die Autkion ausgelaufen ist, ansonsten wurde sie verkauft. Theoretisch könnte eine Auktion auch in den letzten 30 Minuten verkauft worden sein. Ein akurateres Kriterium wäre vermutlich, zu schauen, ob es noch andere Auktionen der gleichen Zeitspanne gab, und ob diese ebenfalls verschwunden sind. Denn es ist unwarscheinlich, dass alle Auktionen in den letzten 30 Minuten verkauft werden, und meine Auktionen haben meist, in Gruppen, die gleich Zeitspanne, da ich sie zum selben Zeitpunkt erstelle.

Der AuctionTracker basiert auf einer, ebenfalls selbst geschriebenen, minimalistischen .net Core Implementierung der Battle.Net Api, welche den Namen BlizzSharp trägt und ebenfalls selbst geschrieben wurde. Die Entwicklung ist jeweils sehr rudimentär, aber wer sich für die Tools interessiert kann sie auf meinem Git Server finden: AuctionTracker

BlizzSharp

BlizzSharp kommuniziert mit der REST Api, und cached die Auktionen bereits rudimentär, da es sich bei dem Tool im aktuellen Zustand nur um ein Proof of Concept handelt, werden weder Fehler abgefangen, noch behandelt. Hier beispielhaft, der Zugriff auf die Auktions API mittels REST:

public async Task<AuctionResponse> GetAuctionsAsync(string realm, string locale)
{
    HttpResponseMessage response = await _http.GetAsync($"auction/data/{realm}?locale={locale}&apikey={_key}");
    if(response.IsSuccessStatusCode)
    {
        string auctionsLocatorResponse = await response.Content.ReadAsStringAsync();
        AuctionLocator locator = JsonConvert.DeserializeObject<Data.AuctionLocatorList>(auctionsLocatorResponse).Locators[0];
        if((locator.Timestamp > _latestTimeStamp) || CurrentAuctionResponse == null)
        {
            using(HttpClient auctionClient = new HttpClient())
            {
                HttpResponseMessage auctionResponse = await auctionClient.GetAsync(locator.Url);
                if(auctionResponse.IsSuccessStatusCode)
                {
                    string auctionsResponse = await auctionResponse.Content.ReadAsStringAsync();
                    PreviousAuctionResponse = CurrentAuctionResponse;
                    CurrentAuctionResponse = JsonConvert.DeserializeObject<AuctionResponse>(auctionsResponse);
                    return CurrentAuctionResponse;
                }
            }

        }
    }
    return null;
}

Wie man sieht wird zuerst die aktuelle Datenbankressource abgefragt, diese ändert sich etwa jede halbe Stunde. Sollte der Zeitstempel der Datenbank neuer sein, als das, was bereits im Cache liegt, so wird die Datenbank heruntergeladen und deserialisiert. Hierzu verwende ich die Newtonsoft.JSON Library, da die REST Api JSON Objekte liefert.

Datenverarbeitung

Die von der Battle.net Api gewonnenen Daten werden anschließend wieder von AuctionTracker ausgewertet. Dabei durchläuft das Tool in einem fixen Zeitintervall ein Update, welches versucht neue Auktionsdaten zu ziehen, sofern vorhanden, und diese mit den Daten des letzten Updates gegenprüft:

private static async Task RunUpdate()
{
    _logger.Info($"Running Update for {_character} on {_realm}");
    AuctionResponse response = await _client.GetAuctionsAsync(_realm, _locale);
    IEnumerable<Auction> auctions = response.Auctions.Where(a => a.Owner == _character);
    if(lastAuctions != null)
    {
        HashSet<Auction> expiredAuctions = new HashSet<Auction>();
        bool removedFound = false;
        foreach(Auction a in lastAuctions)
        {
            if(!auctions.Any(b => b.Id == a.Id))
            {
                //the auction is now gone, but why?
                Item item = await _client.GetItemInfoAsync(a.Item, _locale);
                if(a.TimeLeft == SHORT_DURATION)
                {
                    //the auction probably expired
                    expiredAuctions.Add(a);
                }
                else
                {
                    //the auction was sold
                    await _pushClient.SendNotification($"Your Auction of {a.quantity}x {item.Name} was sold for {FormatCurrency(a.Buyout)}.", "cashregister");
                }
                removedFound = true;
            }
        }
        if(!removedFound)
        {
            _logger.Info("No Expired/Sold Auctions found");
        }
        if(expiredAuctions.Any())
        {
            StringBuilder sb = new StringBuilder();
            foreach(Auction a in expiredAuctions)
            {
                string name = (await _client.GetItemInfoAsync(a.Item, _locale)).Name;
                sb.AppendLine($"{a.quantity}x {name}");
            }
            await _pushClient.SendNotification($"Your Auctions have expired:\n{sb.ToString()}", "none");
        }
    }
    lastAuctions = auctions;
}

Sollte eine Differenz gefunden werden wird noch eine Push Notification an die Push API gesendet, welche die Nachricht an alle Subscriber weiterleitet.

Nachwort

Sollte ein anderer WoW Goblin Interesse an dem Tool haben, kann es gerne unter der gegebenen Lizenz (GPLv3) verwendet werden. Ich plane aktuell nicht, ein öffentliches Service dafür aufzuziehen. Dafür habe ich nicht die nötige Infrastruktur und kann vermutlich auch nicht genug Support geben, damit das auf langfristiger Basis funktionieren könnte.