• Visit Rebornbuddy
  • [Code] poe.ninja item value lookup

    Discussion in 'Archives' started by Apoc, Aug 24, 2017.

    1. Apoc

      Apoc Moderator Staff Member Moderator

      Joined:
      Jan 16, 2010
      Messages:
      2,790
      Likes Received:
      94
      Trophy Points:
      48
      So while I was writing my item evaluator, it dawned on me that I don't need to manually pick and choose which uniques or other things to save or sell. poe.ninja does a fairly good job of tracking unique values, among other things, so I wrote a quick little wrapper to help look up these values.

      Please note; poe.ninja has no idea what a "socket" is, only what a "link" is. So if your item has 6 sockets, but only a 4 link, poe.ninja assumes it's a 0-link and gives you that price. You may want to augment the results of this with searches on poe.trade as well (although that starts to cause a lot of delay since searches are pretty slow over there...)

      Anyhow, here's the code, enjoy/use/abuse (just don't change the update frequency from 2hrs, lets be nice to their servers, shall we?)

      Code:
      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Net;
      using System.Threading.Tasks;
      using Loki.Game.GameData;
      using Loki.Game.Objects;
      using Newtonsoft.Json;
      
      namespace ApocDev.PoE.Trackers
      {
          public class PoeNinjaTracker
          {
              /// <summary>
              /// A list of all cached item information from poe.ninja (non currency and map fragments)
              /// </summary>
              public List<ItemInformation> ItemInformations { get; private set; }
      
              /// <summary>
              /// A list of all cached currency information from poe.ninja (conversion values between currency types)
              /// </summary>
              public List<CurrencyInformation> Currency { get; private set; }
              /// <summary>
              /// A list of all map fragment information from poe.ninja (similar to currency)
              /// </summary>
              public List<CurrencyInformation> Fragments { get; private set; }
      
              /// <summary>
              /// The last time the cached information was updated (information may only be updated once every two hours to save on bandwidth)
              /// </summary>
              public DateTime LastUpdate { get; private set; }
      
              /// <summary>
              /// Returns the current chaos value of the supplied item, or -1 if the item is unknown. 
              /// </summary>
              /// <param name="item"></param>
              /// <returns></returns>
              /// <remarks>
              /// Please note: poe.ninja *only* cares about the number of links on an item, not the number of sockets.
              /// It's possible you may want to check poe.trade as well for the socket count and see where you actually lie.
              /// 
              /// Eg; a 6 socket (but only 4 link) Doomfletch Prism could be ~250c on poe.trade, but poe.ninja will only show ~85c because the *links* are low.
              /// </remarks>
              public double LookupChaosValue(Item item)
              {
                  if(ItemInformations == null)
                      throw new InvalidOperationException("Cannot look up a item's Chaos Orb value until you call Update() to get poe.ninja's item prices.");
      
                  var itemName = item.Name;
                  if (item.Rarity == Rarity.Currency)
                  {
                      return Currency.FirstOrDefault(c => c.CurrencyTypeName == itemName)?.ChaosEquivalent ?? -1;
                  }
                  if (item.HasMetadataFlags(MetadataFlags.MapFragments))
                  {
                      return Fragments.FirstOrDefault(f => f.CurrencyTypeName == itemName)?.ChaosEquivalent ?? -1;
                  }
      
                  var links = item.MaxLinkCount;
      
                  // poe.ninja does this weird handling, where if it's got 4 or less links, they mark it as "0" links
                  // So just do the same so we don't have to get all crazy
                  if (item.MaxLinkCount <= 4)
                      links = 0;
      
                  var info = ItemInformations.FirstOrDefault(i => i.Name == itemName && i.Links == links);
                  if (info == null)
                      return -1;
                  return info.ChaosValue;
              }
      
              /// <summary>
              /// Updates the current item information caches from poe.ninja.
              /// </summary>
              /// <param name="league">"Harbinger", "Hardcore Harbinger", "Standard", "Hardcore"</param>
              /// <returns></returns>
              public async Task Update(string league)
              {
                  // Only run an update every 2 hours, thanks. :)
                  if (LastUpdate.AddHours(2) > DateTime.Now)
                      return;
      
                  var dateSuffix = $"{DateTime.Now.Year}-{DateTime.Now.Day:D2}-{DateTime.Now.Month:D2}";
      
                  string[] urls =
                  {
                      $"http://api.poe.ninja/api/Data/GetEssenceOverview?league={league}&date={dateSuffix}",
                      $"http://api.poe.ninja/api/Data/GetDivinationCardsOverview?league={league}&date={dateSuffix}",
                      $"http://api.poe.ninja/api/Data/GetProphecyOverview?league={league}&date={dateSuffix}",
                      $"http://api.poe.ninja/api/Data/GetUniqueMapOverview?league={league}&date={dateSuffix}",
                      $"http://api.poe.ninja/api/Data/GetMapOverview?league={league}&date={dateSuffix}",
                      $"http://api.poe.ninja/api/Data/GetUniqueJewelOverview?league={league}&date={dateSuffix}",
                      $"http://api.poe.ninja/api/Data/GetUniqueFlaskOverview?league={league}&date={dateSuffix}",
                      $"http://api.poe.ninja/api/Data/GetUniqueWeaponOverview?league={league}&date={dateSuffix}",
                      $"http://api.poe.ninja/api/Data/GetUniqueArmourOverview?league={league}&date={dateSuffix}",
                      $"http://api.poe.ninja/api/Data/GetUniqueAccessoryOverview?league={league}&date={dateSuffix}"
                  };
                  using (var client = new WebClient())
                  {
                      Currency = await GetAndDeserialize<CurrencyInformation>(client, $"http://api.poe.ninja/api/Data/GetCurrencyOverview?league={league}&date={dateSuffix}");
                      Fragments = await GetAndDeserialize<CurrencyInformation>(client, $"http://api.poe.ninja/api/Data/GetFragmentOverview?league={league}&date={dateSuffix}");
      
                      var items = new List<ItemInformation>();
                      foreach (var url in urls)
                      {
                          var tempItems = await GetAndDeserialize<ItemInformation>(client, url);
                          items.AddRange(tempItems);
                      }
      
                      ItemInformations = items;
                  }
      
                  LastUpdate = DateTime.Now;
              }
      
              private async Task<List<T>> GetAndDeserialize<T>(WebClient c, string url)
              {
                  return JsonConvert.DeserializeObject<PoeNinjaLines<T>>(await c.DownloadStringTaskAsync(url)).Lines;
              }
      
              internal class PoeNinjaLines<T>
              {
                  public List<T> Lines { get; set; }
              }
      
              public class CurrencyInformation
              {
                  public string CurrencyTypeName { get; set; }
                  public ValueInfo Pay { get; set; }
                  public ValueInfo Receive { get; set; }
                  public object PaySparkLine { get; set; }
                  public object ReceiveSparkLine { get; set; }
                  public double ChaosEquivalent { get; set; }
      
                  /// <inheritdoc />
                  public override string ToString()
                  {
                      return $"{CurrencyTypeName}, {nameof(ChaosEquivalent)}: {ChaosEquivalent}";
                  }
      
                  public class ValueInfo
                  {
                      public int Id { get; set; }
                      public int LeagueId { get; set; }
                      public int PayCurrencyId { get; set; }
                      public int GetCurrencyId { get; set; }
                      public DateTime SampleTimeUtc { get; set; }
                      public int Count { get; set; }
                      public double Value { get; set; }
                      public int DataPointCount { get; set; }
                  }
              }
      
              public class ItemInformation
              {
                  public int Id { get; set; }
                  public string Name { get; set; }
                  public string Icon { get; set; }
                  public int MapTier { get; set; }
                  public int LevelRequired { get; set; }
                  public string BaseType { get; set; }
                  public int StackSize { get; set; }
                  public string Variant { get; set; }
                  public string ProphecyText { get; set; }
                  public string ArtFilename { get; set; }
                  public int Links { get; set; }
                  public int ItemClass { get; set; }
                  // Really only useful if you care about value changes up or down.
                  public SparkLine Sparkline { get; set; }
                  public List<ItemModifier> ImplicitModifiers { get; set; }
                  public List<ItemModifier> ExplicitModifiers { get; set; }
                  public string FlavourText { get; set; }
                  public string ItemType { get; set; }
                  public double ChaosValue { get; set; }
                  public double ExaltedValue { get; set; }
                  public int Count { get; set; }
      
                  /// <inheritdoc />
                  public override string ToString()
                  {
                      return $"{Name}, {nameof(ChaosValue)}: {ChaosValue}";
                  }
              }
      
              public class ItemModifier
              {
                  public string Text { get; set; }
                  public bool Optional { get; set; }
              }
      
              public class SparkLine
              {
                  public List<float?> Data { get; set; }
                  public float TotalChange { get; set; }
              }
          }
      }
      Usage:

      Code:
               var pnt = new PoeNinjaTracker(); // You should store an instance of this somewhere "static" so you're not constantly hammering stuff.
               // pnt.Update("Harbinger").Wait(); // Synchronous
              await pnt.Update("Harbinger"); // Asynchronous (recommended if available)
                      
              // Simple iterate all items in the current tab and print the item + value if it's >= 1c
              // Note: Example code, so don't run this in the Currency, Essences, or Card tabs.
              foreach (var item in LokiPoe.InGameState.StashUi.InventoryControl.Inventory.Items)
              {
                  var value = pnt.LookupChaosValue(item);
                  if(value < 1)
                      continue;
                  Log.Info(item.FullName + " => " + value + "c");
              }
       
    2. kalanikila

      kalanikila Member

      Joined:
      Nov 28, 2012
      Messages:
      293
      Likes Received:
      5
      Trophy Points:
      18
      How do I use this?
       
    3. darkbluefirefly

      darkbluefirefly Community Developer

      Joined:
      Nov 8, 2013
      Messages:
      1,927
      Likes Received:
      18
      Trophy Points:
      38
      TY Apoc, now we can do on fly calcs on items without needing to load a static list.
       
    4. toNyx

      toNyx Well-Known Member

      Joined:
      Oct 29, 2011
      Messages:
      3,770
      Likes Received:
      35
      Trophy Points:
      48
      dbf is alive, wut. Well be careful posting things like this :D giving more work to comdevs (that actually are comdeving.)
       
    5. Apoc

      Apoc Moderator Staff Member Moderator

      Joined:
      Jan 16, 2010
      Messages:
      2,790
      Likes Received:
      94
      Trophy Points:
      48
      Just be glad I didn't post the poe.trade lookup :)

      This one is just self contained and trivial to use if you end up wanting to use it.

      Also, an updated version that lets you ignore the item's link count, and just look up the max linked item instead (useful for deciding if a 6L is even worth it, etc), it's also been IPlugin'd, so you can just load it as a separate plugin, and anybody else can make use of it. :)

      Code:
      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Net;
      using System.Threading.Tasks;
      using System.Windows.Controls;
      using Buddy.Coroutines;
      using log4net;
      using Loki.Bot;
      using Loki.Common;
      using Loki.Game;
      using Loki.Game.GameData;
      using Loki.Game.Objects;
      using Newtonsoft.Json;
      
      namespace ApocDev.PoE.Trackers
      {
          public class PoeNinjaTracker : IPlugin
          {
              #region Tracker
      
              private static readonly ILog Log = Logger.GetLoggerInstanceForType();
      
              /// <summary>
              ///     A list of all cached item information from poe.ninja (non currency and map fragments)
              /// </summary>
              public static List<ItemInformation> Items { get; private set; }
      
              /// <summary>
              ///     A list of all cached currency information from poe.ninja (conversion values between currency types)
              /// </summary>
              public static List<CurrencyInformation> Currency { get; private set; }
      
              /// <summary>
              ///     A list of all map fragment information from poe.ninja (similar to currency)
              /// </summary>
              public static List<CurrencyInformation> Fragments { get; private set; }
      
              /// <summary>
              ///     The last time the cached information was updated (information may only be updated once every two hours to save on
              ///     bandwidth)
              /// </summary>
              public static DateTime LastUpdate { get; private set; }
      
              /// <summary>
              ///     Returns the current chaos value of the supplied item, or -1 if the item is unknown.
              /// </summary>
              /// <param name="item"></param>
              /// <param name="useMaxLinkEvaluation">
              ///     Force the lookup to use the "maximum links" available on poe.ninja, so we get the
              ///     highest value possible for the item.
              /// </param>
              /// <returns></returns>
              /// <remarks>
              ///     Please note: poe.ninja *only* cares about the number of links on an item, not the number of sockets.
              ///     It's possible you may want to check poe.trade as well for the socket count and see where you actually lie.
              ///     Eg; a 6 socket (but only 4 link) Doomfletch Prism could be ~250c on poe.trade, but poe.ninja will only show ~85c
              ///     because the *links* are low.
              /// </remarks>
              public static double LookupChaosValue(Item item, bool useMaxLinkEvaluation = false)
              {
                  if (Items == null)
                      throw new InvalidOperationException("Cannot look up a item's Chaos Orb value until you call Update() to get poe.ninja's item prices.");
      
                  var itemName = item.Name;
                  if (item.Rarity == Rarity.Currency)
                      return Currency.FirstOrDefault(c => c.CurrencyTypeName == itemName)?.ChaosEquivalent ?? -1;
                  if (item.HasMetadataFlags(MetadataFlags.MapFragments))
                      return Fragments.FirstOrDefault(f => f.CurrencyTypeName == itemName)?.ChaosEquivalent ?? -1;
      
                  // If we want to force a "max links" type of situation on possible 6-socket items
                  if (useMaxLinkEvaluation)
                  {
                      var maxInfo = Items.Where(i => i.Name == itemName).OrderByDescending(i => i.Links).FirstOrDefault();
                      if (maxInfo == null)
                          return -1;
                      return maxInfo.ChaosValue;
                  }
      
                  var links = item.MaxLinkCount;
      
                  // poe.ninja does this weird handling, where if it's got 4 or less links, they mark it as "0" links
                  // So just do the same so we don't have to get all crazy
                  if (item.MaxLinkCount <= 4)
                      links = 0;
      
                  var info = Items.FirstOrDefault(i => i.Name == itemName && i.Links == links);
                  if (info == null)
                      return -1;
                  return info.ChaosValue;
              }
      
              /// <summary>
              ///     Updates the current item information caches from poe.ninja.
              /// </summary>
              /// <param name="league">"Harbinger", "Hardcore Harbinger", "Standard", "Hardcore"</param>
              /// <returns></returns>
              public static async Task Update(string league)
              {
                  // No updates in the overworld, as we don't want to stop any combat related tasks if the update takes more than a few seconds.
                  if (LokiPoe.Me.IsInOverworld)
                      return;
      
                  // Only run an update every 2 hours, thanks. :)
                  if (LastUpdate.AddHours(2) > DateTime.Now)
                      return;
      
                  Log.Info("[PoeNinjaTracker] Updating item caches...");
      
                  // This should probably be UTC. I haven't confirmed whether or not this rolls over at UTC times or not, haven't really been awake when it happens.
                  var dateSuffix = $"{DateTime.UtcNow.Year}-{DateTime.UtcNow.Day:D2}-{DateTime.UtcNow.Month:D2}";
      
                  string[] urls =
                  {
                      $"http://api.poe.ninja/api/Data/GetEssenceOverview?league={league}&date={dateSuffix}",
                      $"http://api.poe.ninja/api/Data/GetDivinationCardsOverview?league={league}&date={dateSuffix}",
                      $"http://api.poe.ninja/api/Data/GetProphecyOverview?league={league}&date={dateSuffix}",
                      $"http://api.poe.ninja/api/Data/GetUniqueMapOverview?league={league}&date={dateSuffix}",
                      $"http://api.poe.ninja/api/Data/GetMapOverview?league={league}&date={dateSuffix}",
                      $"http://api.poe.ninja/api/Data/GetUniqueJewelOverview?league={league}&date={dateSuffix}",
                      $"http://api.poe.ninja/api/Data/GetUniqueFlaskOverview?league={league}&date={dateSuffix}",
                      $"http://api.poe.ninja/api/Data/GetUniqueWeaponOverview?league={league}&date={dateSuffix}",
                      $"http://api.poe.ninja/api/Data/GetUniqueArmourOverview?league={league}&date={dateSuffix}",
                      $"http://api.poe.ninja/api/Data/GetUniqueAccessoryOverview?league={league}&date={dateSuffix}"
                  };
                  using (var client = new WebClient())
                  {
                      Currency = await GetAndDeserialize<CurrencyInformation>(client, $"http://api.poe.ninja/api/Data/GetCurrencyOverview?league={league}&date={dateSuffix}");
                      Fragments = await GetAndDeserialize<CurrencyInformation>(client, $"http://api.poe.ninja/api/Data/GetFragmentOverview?league={league}&date={dateSuffix}");
      
                      var items = new List<ItemInformation>();
                      foreach (var url in urls)
                      {
                          var tempItems = await GetAndDeserialize<ItemInformation>(client, url);
                          items.AddRange(tempItems);
                      }
      
                      Items = items;
                  }
      
                  LastUpdate = DateTime.Now;
              }
      
              private static async Task<List<T>> GetAndDeserialize<T>(WebClient c, string url)
              {
                  var downloadTask = c.DownloadStringTaskAsync(url);
                  // Special handling for being inside a Coroutine, we need to await an external task.
                  if (Coroutine.Current != null)
                      downloadTask = Coroutine.ExternalTask(downloadTask);
      
                  return JsonConvert.DeserializeObject<PoeNinjaLines<T>>(await downloadTask).Lines;
              }
      
              // Used internally to help with JSON deserializing.
              internal class PoeNinjaLines<T>
              {
                  public List<T> Lines { get; set; }
              }
      
              public class CurrencyInformation
              {
                  public string CurrencyTypeName { get; set; }
                  public ValueInfo Pay { get; set; }
                  public ValueInfo Receive { get; set; }
                  public object PaySparkLine { get; set; }
                  public object ReceiveSparkLine { get; set; }
                  public double ChaosEquivalent { get; set; }
      
                  /// <inheritdoc />
                  public override string ToString()
                  {
                      return $"{CurrencyTypeName}, {nameof(ChaosEquivalent)}: {ChaosEquivalent}";
                  }
      
                  public class ValueInfo
                  {
                      public int Id { get; set; }
                      public int LeagueId { get; set; }
                      public int PayCurrencyId { get; set; }
                      public int GetCurrencyId { get; set; }
                      public DateTime SampleTimeUtc { get; set; }
                      public int Count { get; set; }
                      public double Value { get; set; }
                      public int DataPointCount { get; set; }
                  }
              }
      
              public class ItemInformation
              {
                  public int Id { get; set; }
                  public string Name { get; set; }
                  public string Icon { get; set; }
                  public int MapTier { get; set; }
                  public int LevelRequired { get; set; }
                  public string BaseType { get; set; }
                  public int StackSize { get; set; }
                  public string Variant { get; set; }
                  public string ProphecyText { get; set; }
                  public string ArtFilename { get; set; }
                  public int Links { get; set; }
      
                  public int ItemClass { get; set; }
      
                  // Really only useful if you care about value changes up or down.
                  public SparkLine Sparkline { get; set; }
      
                  public List<ItemModifier> ImplicitModifiers { get; set; }
                  public List<ItemModifier> ExplicitModifiers { get; set; }
                  public string FlavourText { get; set; }
                  public string ItemType { get; set; }
                  public double ChaosValue { get; set; }
                  public double ExaltedValue { get; set; }
                  public int Count { get; set; }
      
                  /// <inheritdoc />
                  public override string ToString()
                  {
                      return $"{Name}, {nameof(ChaosValue)}: {ChaosValue}";
                  }
              }
      
              public class ItemModifier
              {
                  public string Text { get; set; }
                  public bool Optional { get; set; }
              }
      
              public class SparkLine
              {
                  public List<float?> Data { get; set; }
                  public float TotalChange { get; set; }
              }
      
              #endregion
      
              #region IPlugin Stuff
      
              /// <inheritdoc />
              public UserControl Control { get; }
      
              /// <inheritdoc />
              public JsonSettings Settings { get; }
      
              /// <inheritdoc />
              public MessageResult Message(Message message)
              {
                  return MessageResult.Unprocessed;
              }
      
              /// <inheritdoc />
              public string Name { get; } = "PoeNinjaTracker";
      
              /// <inheritdoc />
              public string Author { get; } = "Apoc";
      
              /// <inheritdoc />
              public string Description { get; } = "Uses poe.ninja to track the cost of items in chaos and/or exalted orbs.";
      
              /// <inheritdoc />
              public string Version { get; } = "1.0.0.0";
      
              /// <inheritdoc />
              public void Initialize()
              {
              }
      
              /// <inheritdoc />
              public void Deinitialize()
              {
              }
      
              /// <inheritdoc />
              public void Enable()
              {
              }
      
              /// <inheritdoc />
              public void Disable()
              {
              }
      
              /// <inheritdoc />
              public async Task<LogicResult> Logic(Logic logic)
              {
                  // Update from poe.ninja.
                  // This is safe to call constantly as we have a date/time check enabled.
                  await Update(LokiPoe.Me.League);
                  return LogicResult.Unprovided;
              }
      
              #endregion
          }
      }
      Usage:

      Code:
                  ApocDev.PoE.Trackers.PoeNinjaTracker.Update(LokiPoe.Me.League).Wait(); // Synchronous
                  await ApocDev.PoE.Trackers.PoeNinjaTracker.Update(LokiPoe.Me.League); // Asynchronous (recommended)
      
                  var chaosValue = ApocDev.PoE.Trackers.PoeNinjaTracker.LookupChaosValue(myItem, true);
      
       
    6. Rintf

      Rintf Member

      Joined:
      Jun 15, 2012
      Messages:
      31
      Likes Received:
      1
      Trophy Points:
      8
      Can give a baisc guide on how to use this? i got to manually look up poe trade or poe ninja for items value...
       

    Share This Page