diff --git a/spec/System/TestTradeHelpers_spec.lua b/spec/System/TestTradeHelpers_spec.lua index 8b61757af1..806116e4e3 100644 --- a/spec/System/TestTradeHelpers_spec.lua +++ b/spec/System/TestTradeHelpers_spec.lua @@ -21,7 +21,15 @@ describe("TradeHelpers trade hash matching", function() end) describe("findTradeIdOption", function() - it("matches a '#'-valued option and returns its value", function() + it("matches a flattened option stat and returns its value", function() + local tradeId, value = tradeHelpers.findTradeIdOption( + "Passive skills in radius of Elemental Overload can be Allocated without being connected to your tree", + "explicit") + assert.equal("explicit.stat_2422708892", tradeId) + assert.equal(22088, value) + end) + + it("matches a legacy '#'-valued option and returns its value", function() local tradeId, value = tradeHelpers.findTradeIdOption("Grants Level 20 Summon Bestial Snake Skill", "explicit") assert.equal("explicit.stat_2878779644", tradeId) diff --git a/spec/System/TestTradeQueryRequests_spec.lua b/spec/System/TestTradeQueryRequests_spec.lua index 0e731701f8..fb3ffd9a57 100644 --- a/spec/System/TestTradeQueryRequests_spec.lua +++ b/spec/System/TestTradeQueryRequests_spec.lua @@ -1,4 +1,5 @@ describe("TradeQueryRequests", function() + local dkjson = require "dkjson" local mock_limiter = { NextRequestTime = function() return os.time() @@ -192,6 +193,51 @@ Strict-Transport-Security: max-age=63115200; includeSubDomains; preload]] end) end) + describe("FetchResultBlock", function() + it("reads weighted sums from current and legacy pseudo mods", function() + local function makeTradeEntry(id, pseudoMods) + return { + id = id, + listing = { + price = { amount = 1, currency = "chaos", type = "~price" }, + whisper = "hi", + account = { name = "seller" }, + }, + item = { + extended = { text = "VGVzdCBJdGVt" }, + pseudoMods = pseudoMods, + }, + } + end + local response = dkjson.encode({ + result = { + makeTradeEntry("current", { { description = "Sum: 178", domain = "pseudo", hash = "stat.statgroup.0" } }), + makeTradeEntry("legacy", { "Sum: 42" }), + makeTradeEntry("empty", { }), + }, + }) + local fetchedItems + local callbackError + requests.requestQueue.fetch = { } + requests:FetchResultBlock("test", function(items, errMsg) + fetchedItems = items + callbackError = errMsg + end) + + local request = table.remove(requests.requestQueue.fetch, 1) + request.callback(response) + + local itemsById = { } + for _, item in ipairs(fetchedItems) do + itemsById[item.id] = item + end + assert.is_nil(callbackError) + assert.are.equal("178", itemsById.current.weight) + assert.are.equal("42", itemsById.legacy.weight) + assert.are.equal("0", itemsById.empty.weight) + end) + end) + describe("FetchResults", function() -- Pass: Fetches exactly 10 from 11, in 1 block -- Fail: Fetches wrong count/blocks, indicating batch limit violation, triggering rate limits diff --git a/src/Classes/TradeHelpers.lua b/src/Classes/TradeHelpers.lua index 5baeaaeca2..853404e285 100644 --- a/src/Classes/TradeHelpers.lua +++ b/src/Classes/TradeHelpers.lua @@ -79,15 +79,54 @@ end local _optionTradeStatMap +-- These option stats are still needed for legacy items, but are no longer +-- included in the trade API's 3.29 stats response. +local legacyOptionTradeStats = { + { + type = "explicit", + id = "explicit.stat_2878779644", + text = "Grants Level 20 Summon Bestial # Skill", + options = { + { id = 1, text = "rhoa" }, + { id = 2, text = "ursa" }, + { id = 3, text = "snake" }, + }, + }, + { + type = "explicit", + id = "explicit.stat_3642528642", + text = "Only affects Passives in # Ring", + options = { + { id = 1, text = "small" }, + { id = 2, text = "medium" }, + { id = 3, text = "large" }, + { id = 4, text = "very large" }, + { id = 5, text = "massive" }, + }, + }, +} + ---@param tradeStats table table of data from https://www.pathofexile.com/api/trade2/data/stats ---@return table optionTradeStatMap table containing helper data for matching trade option filters local function getOptionTradeStatMap(tradeStats) if _optionTradeStatMap then return _optionTradeStatMap end - local optionTradeStatMap = {} + local optionTradeStatMap = { + exact = {}, + patterns = {}, + } for _, cat in ipairs(tradeStats) do if cat.id == "enchant" or cat.id == "explicit" or cat.id == "implicit" then for _, entry in ipairs(cat.entries) do - if entry.option and entry.text:match("#") then + local tradeId, optionId = entry.id:match("^(.-)|(.+)$") + if tradeId and optionId then + -- The 3.29 trade API expands option stats into one entry per option, + -- with the option value appended to the stat ID. + local matchKey = entry.text:gsub(" Passage", ""):lower() + optionTradeStatMap.exact[cat.id .. "\0" .. matchKey] = { + tradeId = tradeId, + value = tonumber(optionId) or optionId, + } + elseif entry.option and entry.text:match("#") then -- pob parses the passage part as a separate mod line, which -- causes trouble local matchKey = entry.text:gsub("#", "(.*)"):gsub(" Passage", ""):lower() @@ -95,11 +134,19 @@ local function getOptionTradeStatMap(tradeStats) for _, option in ipairs(entry.option.options) do option.text = option.text and option.text:lower() end - optionTradeStatMap[matchKey] = { type = cat.id, options = entry.option.options, tradeId = entry.id } + optionTradeStatMap.patterns[matchKey] = { type = cat.id, options = entry.option.options, tradeId = entry.id } end end end end + for _, entry in ipairs(legacyOptionTradeStats) do + local matchKey = entry.text:gsub("#", "(.*)"):lower() + optionTradeStatMap.patterns[matchKey] = optionTradeStatMap.patterns[matchKey] or { + type = entry.type, + options = entry.options, + tradeId = entry.id, + } + end _optionTradeStatMap = optionTradeStatMap return _optionTradeStatMap @@ -165,7 +212,11 @@ function M.findTradeIdOption(modLine, modType) -- reformat double-line cluster enchants modLine = modLine:gsub(".added small passive skills grant: ", " ") - for pat, entry in pairs(optionTradeStatMap) do + local exactEntry = optionTradeStatMap.exact[modType .. "\0" .. modLine] + if exactEntry then + return exactEntry.tradeId, exactEntry.value + end + for pat, entry in pairs(optionTradeStatMap.patterns) do local match = modLine:match(pat) if entry.type == modType and match then for _, option in ipairs(entry.options) do diff --git a/src/Classes/TradeQueryRequests.lua b/src/Classes/TradeQueryRequests.lua index 84f6e39770..4bdc5e3372 100644 --- a/src/Classes/TradeQueryRequests.lua +++ b/src/Classes/TradeQueryRequests.lua @@ -290,6 +290,8 @@ function TradeQueryRequestsClass:FetchResultBlock(url, callback) end local items = {} for _, trade_entry in pairs(response.result) do + local pseudoMod = trade_entry.item.pseudoMods and trade_entry.item.pseudoMods[1] + local pseudoModLine = pseudoMod and (pseudoMod.description or pseudoMod) table.insert(items, { amount = trade_entry.listing.price.amount, currency = trade_entry.listing.price.currency, @@ -297,7 +299,7 @@ function TradeQueryRequestsClass:FetchResultBlock(url, callback) item_string = escapeGGGString(common.base64.decode(trade_entry.item.extended.text)), whisper = trade_entry.listing.whisper, trader = trade_entry.listing.account.name, - weight = trade_entry.item.pseudoMods and trade_entry.item.pseudoMods[1]:match("Sum: (.+)") or "0", + weight = pseudoModLine and pseudoModLine:match("Sum: (.+)") or "0", id = trade_entry.id }) end