Apoc
Well-Known Member
- Joined
- Jan 16, 2010
- Messages
- 2,790
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?)
Usage:
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");
}