Trading Post APIs

Developer
Aug 24, 2014 at 1:26 PM
Edited Aug 24, 2014 at 1:30 PM
Hey

I want to discuss whether we should have a Trading Post API in GW2.NET, considering that the TP is a large aspect of the game. Bigger than for example /v1/files or /v2/quaggans.

We already know that an official /v2 Trading Post API is on its way (/v2/commerce/...), and that it will offer at least read-only access to trading post data. There is also the unofficial API that the game client itself uses to buy and sell.

The question is whether we should support the official API or the unofficial API, neither or both?

Also, can we be held accountable for abuse if we provide easy access to the unofficial API? I know of a few sites that use the unofficial API, but none of them provide the tools needed to access the APIs directly the way we do.
Coordinator
Aug 26, 2014 at 6:19 PM
I'll look into it, I thought about it for a longer time.
Coordinator
Sep 3, 2014 at 1:03 PM
Ok, after thinking about it for a few days. I think we should go with a read only approach first. With this we don't run into risks of abuse (sinde the user can't do anything ingame from outside), but we enable the user to watch the TP in real time.
I'm still not sure if we should offer a separate library or integrate it into the main project.
Developer
Sep 4, 2014 at 8:52 AM
Let's see what happens when the feature pack is released on September 9. Anet is changing how the trading post works, and probably the APIs that go along with it. I'm fairly sure that the current version of the unofficial API will go offline sometime soon. We can only hope that they are changing the game client to use the same /v2/commerce APIs that we're supposed to use.
Coordinator
Sep 4, 2014 at 5:46 PM
Yeah. Thought so too. Wouldn't make any sense to work on it now for nothing.
Developer
Sep 4, 2014 at 6:33 PM
Edited Sep 4, 2014 at 6:38 PM
News post is up: https://www.guildwars2.com/en/news/introducing-the-new-trading-post/
We’ve never really made a secret of the fact that the Trading Post is a very custom web application built for running inside the game. Building those pieces of the interface that way has provided us with a ton of benefits. It’s also brought with it a unique set of challenges, both in matching the rest of the UI and ensuring that performance is acceptable. Definitely the most visible downside to that decision has been the slow initial loading for the entire Black Lion Trading Company panel. A huge part of the work we’ve done on improving the overall Trading Post experience has been focusing on performance from a technology perspective. To that end, we’re introducing a completely new browser engine into the game. When building the new Trading Post on top of this new, faster browser engine, we’ve also biased our technical decisions toward making things fast.

When looking at the big picture of where time was spent when loading the old Trading Post, a few places that were very expensive stood out. We were able to gain huge performance improvements by updating the game client so that the embedded browser could access things the game already knows about. There’s no reason we should have to download an icon for an in-game item from the Trading Post servers; it’s already in the game files. This same optimization can also be made for basic item information like name, description, and vendor price. In the new Trading Post, we avoid going out to a server whenever possible, and the experience is faster and better for it.
I'm not sure if by "browser engine" they mean a simple HTTP client, or an actual browser with a graphics rendering engine. What ton of benefits do they even mean? I can't see anything about how the trading post works that they couldn't implement in native code instead of HTML.
Developer
Sep 5, 2014 at 9:02 PM
http://www.twitch.tv/guildwars2/b/565458903 (skip to 32:00)

Long story short: they are keeping the unofficial API, and /v2/commerce will have to wait another year or so.
Coordinator
Sep 5, 2014 at 10:34 PM
I'm not sure if by "browser engine" they mean a simple HTTP client, or an actual browser with a graphics rendering engine.
I think it's the latter. Since the Trading Post ist really just a web site running in the game. If you look at the unofficial trading post api you can get the exact same TP you see ingame.
Also sorta "confirmed" with this:
We were able to gain huge performance improvements by updating the game client so that the embedded browser could access things the game already knows about.

Long story short: they are keeping the unofficial API, and /v2/commerce will have to wait another year or so.
So we can work on it. Fine by me.
Developer
Sep 6, 2014 at 12:08 PM
Now I'm curious about how they are changing the game client to serve "things the game already knows about". The browser engine can only access files by their logical path, right? So the game client needs to be able to serve game files for file paths or URIs.
I think Anet may have integrated a local HTTP server so that the browser engine can load game files from localhost over HTTP.
Developer
Sep 9, 2014 at 8:53 AM
Sep 9, 2014 at 11:16 AM
All hale ArenaNet :)
Developer
Sep 9, 2014 at 11:37 AM
Can't wait to get home from work and start working on this. Hopefully there will be some documentation up by then.
Sep 9, 2014 at 1:58 PM
Developer
Sep 9, 2014 at 2:06 PM
Perfect. Thanks. :)

Two more hours, and then traffic. Gah!
Developer
Sep 10, 2014 at 9:12 PM
Edited Sep 10, 2014 at 9:12 PM
Initial implementations should go up by the end of the week. And if not, I'll make sure it gets done by the end of the weekend. The commerce APIs are a little more buggy than I expected them to be after so much waiting, but I'll manage.
Developer
Sep 11, 2014 at 10:23 PM
Initial support for /v2/commerce/listings has been added to the source code. Feel free to download and compile it yourself. Other /v2/commerce services will follow over the next couple of days.
Developer
Sep 12, 2014 at 11:08 PM
I pushed version 0.9.9.0 with support for /v2/commerce services.

Note:
  • I changed the interfaces for v2 to a generic repository pattern.
  • The Service2Manager class has been replaced by the ServiceFactory class.
Enjoy!

Usage example
// Get an instance of the item service (legacy v1 service)
IItemService itemService = new ServiceManager();

// Get an instance of the price service (/v2/commerce/prices)
IRepository<int, AggregateListing> priceService = ServiceFactory.Default().GetPriceService();

// Discover valid item identifiers
var itemIds = await priceService.DiscoverAsync();

// Take the first 200 identifiers
itemIds = itemIds.Take(200).ToList();

// Get 200 listings
foreach (var itemId in itemIds)
{
    // Get the listing for the current item identifier
    var listing = await priceService.FindAsync(itemId);

    // Get the item details
    listing.Item = await itemService.GetItemDetailsAsync(itemId);

    // Print the item name
    Console.WriteLine("Item: " + listing.Item.Name);

    // Print the highest bids
    Console.WriteLine("Highest bids: " + listing.BuyOffers.UnitPrice);

    // Print the lowest offers
    Console.WriteLine("Lowest offers: " + listing.SellOffers.UnitPrice);
}
Developer
Sep 13, 2014 at 10:43 PM
Edited Sep 14, 2014 at 12:19 AM
Here's something I put together in xaml for Windows 8 as a proof of concept.

If only my UI design skills were anywhere near as good as my C# skills. :(

Image
Coordinator
Sep 22, 2014 at 9:29 AM
If only my UI design skills were anywhere near as good as my C# skills. :(
Heh, I can totally understand you. If I look at my UI skills.... urgh, let'S not talk about them. Still this pic looks good, we could use it as an additional example.
Sep 25, 2014 at 8:52 PM
https://forum-en.guildwars2.com/forum/community/api/v2-items-Enabled/first#post4444973

Hey folks, we’ve just enabled /v2/items. Documentation isn’t quite ready yet but I’ll be talking to poke and we’ll go from there.
Querying it is identical to the other /v2 APIs.
https://api.guildwars2.com/v2/items/12138
https://api.guildwars2.com/v2/items?page=0
https://api.guildwars2.com/v2/items?page=1&page_size=200
https://api.guildwars2.com/v2/items?ids=12138,57
Developer
Sep 25, 2014 at 8:59 PM
Developer
Sep 26, 2014 at 8:25 AM
Edited Sep 27, 2014 at 2:23 PM
There is a bug that causes /v2/items to return incorrect data for upgrade components. Instead of working around the problem, I think it's better to wait for an official fix. That means that for now, you will have to fall back to /v1/item_details.json for upgrade component details. General item details (name, description, level, ...) will still work as expected for all item types.

edit

It's been fixed.
Developer
Sep 27, 2014 at 2:23 PM
Coordinator
Sep 27, 2014 at 3:03 PM
seems fixed:
https://api.guildwars2.com/v2/items/56

default_skin is now a number, like it should’ve been all along

https://api.guildwars2.com/v2/items/21092

UpgradeComponents now have a proper details object, like they should’ve had all along.

Sorry about the bugs! We have lots of items, some are weird XD
Developer
Sep 27, 2014 at 3:20 PM
Edited Sep 27, 2014 at 3:58 PM
I'm trying to come up with some good examples to put in the wiki. Any suggestions? I'm mainly concerned about getting the code syntax across, because it is WILDLY different from /v1 services.

https://gw2dotnet.codeplex.com/wikipage?title=Repository&referringTitle=Documentation
Developer
Sep 27, 2014 at 7:23 PM
Developer
Sep 27, 2014 at 9:28 PM
Hey guys, I started integrating the use of the new v2 ItemService (I know you JUST added it), but I'm getting an exception when calling FindAll():
GW2DotNET.Common.ServiceException: unable to use 'all' keyword for this API.
   at GW2DotNET.Common.ServiceClient.OnError(HttpWebResponse response, ISerializerFactory serializerFactory, IConverter`2 gzipInflator)
   at GW2DotNET.Common.ServiceClient.Send[TResult](IRequest request)
   at GW2DotNET.V2.Items.ItemService.FindAll(ICollection`1 identifiers)
   at GW2PAO.API.Services.CommerceService.GetItems(ICollection`1 itemIDs) in d:\Users\Sam\Source\Repos\gw2pao\GW2PAO.API\Services\CommerceService.cs:line 158
Does the v2 items API not support multiple item IDs in one request?
Developer
Sep 27, 2014 at 9:35 PM
Wait.... nevermind. I was passing 0 item IDs and didn't realize it. Woops! It's been a long day...
Developer
Sep 27, 2014 at 9:41 PM
Edited Sep 27, 2014 at 9:42 PM
We do have that problem when an empty collection is passed in. The fall back value is "ids=all", because an empty "ids" parameter is not supported by the API.

https://api.guildwars2.com/v2/items?ids=

Notice how it just returns identifiers?

Fallback value: https://api.guildwars2.com/v2/items?ids=all

Unfortunately, that is also not supported.
Coordinator
Sep 29, 2014 at 6:54 PM
Edited Sep 29, 2014 at 6:55 PM
On a sidenote: I think we should throw an exception if no ID is passed to the method. Returning a collection of all elements is not nearly as costly as in V1, but the user not want a complete collection.
Also It is not what I expect from the API and the wrapper. If a operation is not supported I expect to return an error/throw an exception, not returning something I did not specifically requested.
I'm trying to come up with some good examples to put in the wiki. Any suggestions?
The article looks very good. I don't think a user will have any problems with it. Maybe the introduction could use a little more text, but this is complaining on a high note. Another thing is var. While it may be useful I think it is not beneficial to examples, since it obscures the understanding of the code, especially in more advanced examples.
Developer
Sep 29, 2014 at 8:55 PM
Edited Sep 29, 2014 at 8:58 PM
It kind of makes sense though. How is FindAll(null) or Findall(empty collection) really that much different from FindAll() without parameters?

Maybe my intentions are more clear when I write it like this: FindAll(ICollection<int> identifiers = null)
Developer
Sep 30, 2014 at 8:25 AM
Edited Sep 30, 2014 at 8:29 AM
Okay, how about if I change the default value to "ids=-1" and hope that it results in HTTP 400?

Works for some, but not for all of them...
https://api.guildwars2.com/v2/items?ids=-1
https://api.guildwars2.com/v2/commerce/listings?ids=-1
https://api.guildwars2.com/v2/commerce/prices?ids=-1
https://api.guildwars2.com/v2/quaggans?ids=-1

The alternative is ensure that the collection is not empty, or throw an exception. That solution is not thread-safe however. A different thread may call collection.Clear() after we performed the check, but before we converted the collection to a query string. We could just copy the collection to a local variable first, but that's over-engineering the problem.
Coordinator
Sep 30, 2014 at 3:17 PM
Edited Sep 30, 2014 at 3:26 PM
It kind of makes sense though. How is FindAll(null) or Findall(empty collection) really that much different from FindAll() without parameters?
I think in this case the FindAll() method is a bit ambiguous here. Do you want to get all items with details, which would result in the the API returning a list of all items, or do you want to get a list of all item IDs?
Personally? I think I'd want to get a collection of Items, not IDs.
This is the same for the FindAll(null) method, since we are passing nothing to the URL parameter. This however goes not for the FindAll(empty collection). We are passing an empty collection to the method, which is different from null or not passing a parameter at all.

Passing nothing, therefore calling: https://api.guildwars2.com/v2/items is something different than passing an empty collection, therefore calling: https://api.guildwars2.com/v2/items?ids=

Since the ItemsService returns a collection of items, not just a collection of IDs we should disallow the use of FindAll() and FindAll(null), since this is how I personally would want the library to react.
Developer
Sep 30, 2014 at 3:32 PM
Edited Sep 30, 2014 at 3:35 PM
This is getting confusing. Can you describe for each of these what should happen exactly?
  • FindAll();
  • FindAll(null);
  • FindAll(new int[0]);
  • FindAll(new[] { 1, 2, 3 });
Example based on the current implementation:
  • FindAll();
    • Find details for all identifiers (not always supported by the server at this time)
  • FindAll(null);
    • throw ArgumentNullException
  • FindAll(new int[0]);
    • Find details for all identifiers (same as FindAll() without parameters due to not supported empty ids)
  • FindAll(new[] { 1, 2, 3 });
    • Find details for identifiers 1, 2 and 3
Coordinator
Sep 30, 2014 at 3:42 PM
Yeah, after reading it again it is a bit confusing. I'll try to piece something together when I'm back home.
Developer
Sep 30, 2014 at 3:44 PM
Edited Sep 30, 2014 at 4:06 PM
Actually, it's so simple that I can't believe I didn't think of this before. If we get an empty collection, there is absolutely no need to call the service at all. Just return an empty collection!

Suggested fix
  • FindAll();
    • Find all details
    • If disabled by the server, throw ServiceException
    • Underlying service: ?ids=all
  • FindAll(new[] { 1, 2, 3 });
    • Find all details with identifiers 1, 2 or 3
    • Underlying service: ?ids=1,2,3
  • FindAll(null);
    • throw ArgumentNullException
    • Semantic equivalent: ?ids=null
  • FindAll(new int[0]);
    • immediately return an empty collection of details without ever contacting the service
    • Semantic equivalent: ?ids=
Coordinator
Sep 30, 2014 at 8:02 PM
Yup that's pretty much what I wanted to do. In the case of v2/items we would throw a ServiceException for FindAll(), return a collection of details for FindAll(some collection), throw an ArgumentNullException for FindAll(null) and return an empty collection for FindAll(some empty collection).

For v2/Quaggans it would be the the same, except for FindAll(), which would return a complete quaggan collection.
Developer
Oct 1, 2014 at 7:48 PM
Edited Oct 1, 2014 at 7:49 PM
I forgot about one little detail. We can't get the X-Result-Total value unless we make a call to the server. So I guess ArgumentException is a better way to go.
Coordinator
Oct 1, 2014 at 8:01 PM
You mean for the FindAll(null) method, or the FindAll() method?
Developer
Oct 1, 2014 at 8:08 PM
Edited Oct 1, 2014 at 8:11 PM
The other one, FindAll(empty collection). We can't call the service without at least 1 identifier, and we also can't get the X-Result-Total header without calling the service. The only meaningful thing to do is throw an exception explaining that the collection cannot be empty.
if (identifiers.Count == 0)
{
    throw new ArgumentOutOfRangeException(
    "identifiers",
    "Precondition failed: identifiers.Count > 0");
}
Developer
Oct 1, 2014 at 8:29 PM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.