Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions spec/System/TestCommon_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,36 @@ describe("Common", function()
-- common.classes.StupidClass = nil
-- end)
end)
-- tests for headless wrapper implementation of deflate/inflate. disabled until support is added
-- describe("Deflate and Inflate", function()
-- it("round-trips a simple string", function()
-- local text = "Hello my name is ????!"
-- local compressed = Deflate(text)
-- assert.is_not_nil(compressed)
-- assert.are.equal(text, Inflate(compressed))
-- end)
-- it("produces a zlib header", function()
-- local compressed = Deflate("some data to compress")
-- assert.are.equal(0x78, compressed:byte(1))
-- end)
-- it("round-trips an empty string", function()
-- local compressed = Deflate("")
-- assert.is_not_nil(compressed)
-- assert.are.equal("", Inflate(compressed))
-- end)
-- it("round-trips data larger than the 16k buffer", function()
-- local text = string.rep("The quick brown fox jumps over the lazy dog. ", 5000)
-- local compressed = Deflate(text)
-- assert.is_true(#compressed < #text)
-- assert.are.equal(text, Inflate(compressed))
-- end)
-- it("round-trips binary data", function()
-- local bytes = {}
-- for i = 0, 255 do
-- bytes[i + 1] = string.char(i)
-- end
-- local text = table.concat(bytes)
-- assert.are.equal(text, Inflate(Deflate(text)))
-- end)
-- end)
end)
27 changes: 16 additions & 11 deletions spec/System/TestCompareBuySimilar_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,13 @@ Implicits: 1
controls.mod1Check.state = true
controls.mod1Check.changeFunc(true)
controls.search.onClick()
local queryJson = copiedUrl:match("%?q=(.*)"):gsub("%%(%x%x)", function(hex)
return string.char(tonumber(hex, 16))
end)
local query = require("dkjson").decode(queryJson)
-- disabled for now due to headless wrapper lacking zlib bindings
-- local queryB64 = copiedUrl:match("Test%%20League/(.*)$"):gsub("%%(%x%x)", function(hex)
-- return string.char(tonumber(hex, 16))
-- end)
-- local query = require("dkjson").decode(require("Classes.TradeHelpers").B64GzipDecode(queryB64))

assert.same({ { type = "and", filters = { { id = "explicit.stat_1526933524" } } } }, query.query.stats)
-- assert.same({ { type = "and", filters = { { id = "explicit.stat_1526933524" } } } }, query.stats)
end)

it("rebuilds the URL when league and listed status change", function()
Expand All @@ -165,13 +166,17 @@ Implicits: 1
controls.leagueDrop:SetSel(2)
controls.search.onClick()
assert.not_equal(initialUrl, copiedUrl)
assert.is_truthy(copiedUrl:find("/Standard?", 1, true))
local standardUrl = copiedUrl
assert.is_truthy(copiedUrl:find("/Standard/", 1, true))

controls.listedDrop:SetSel(4)
controls.search.onClick()
assert.not_equal(standardUrl, copiedUrl)
assert.is_truthy(copiedUrl:find("any", 1, true))
-- disabled for now due to headless wrapper lacking zlib bindings
-- local standardUrl = copiedUrl

-- controls.listedDrop:SetSel(4)
-- controls.search.onClick()
-- assert.not_equal(standardUrl, copiedUrl)
-- local b64 = copiedUrl:match("Standard/(.-)$")
-- local json = require("Classes.TradeHelpers").B64GzipDecode(b64)
-- assert.is_truthy(json:find("any", 1, true))
end)

it("persists popup selector choices", function()
Expand Down
14 changes: 14 additions & 0 deletions spec/System/TestTradeHelpers_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,18 @@ describe("TradeHelpers trade hash matching", function()
assert.is_nil(tradeHelpers.findTradeIdOption("+100 to IQ", "explicit"))
end)
end)
-- disabled for now since the headless wrapper has no zlib bindings
-- it("gzip decode", function()
-- local sampleText = "Test string please ignore"
-- local gzipped = tradeHelpers.B64GzipEncode(sampleText)
-- local roundTrip = tradeHelpers.B64GzipDecode(gzipped)

-- assert.are.Equal(sampleText, roundTrip)
-- assert.are_not_equal(sampleText, gzipped)

-- local longText = string.rep("12345678", 4096)
-- local gzippedLong = tradeHelpers.B64GzipEncode(longText)
-- local longRoundTrip = tradeHelpers.B64GzipDecode(gzippedLong)
-- assert.are.Equal(longText, longRoundTrip)
-- end)
end)
4 changes: 2 additions & 2 deletions spec/System/TestTradeQueryGenerator_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ describe("TradeQueryGenerator", function()
}
queryGen.tradeTypeIndex = 1
local query
queryGen.requesterCallback = function(_, queryJson)
query = require("dkjson").decode(queryJson).query
queryGen.requesterCallback = function(_, queryTable)
query = queryTable
end
queryGen:FinishQuery()

Expand Down
31 changes: 14 additions & 17 deletions src/Classes/CompareBuySimilar.lua
Original file line number Diff line number Diff line change
Expand Up @@ -58,25 +58,22 @@ local function buildURL(item, slotName, controls, modEntries, defenceEntries, is

-- Build query
local queryTable = {
query = {
status = { option = listedApiValue },
stats = {
{
type = "and",
filters = {}
}
},
status = { option = listedApiValue },
stats = {
{
type = "and",
filters = {}
}
},
sort = { price = "asc" }
}
local queryFilters = {}

if isUnique then
-- Search by unique name
-- Strip "Foulborn" prefix from unique name for trade search
local tradeName = (item.title or item.name):gsub("^Foulborn%s+", "")
queryTable.query.name = tradeName
queryTable.query.type = item.baseName
queryTable.name = tradeName
queryTable.type = item.baseName
-- If item is Foulborn, add the foulborn_item filter
if item.foulborn then
queryFilters.misc_filters = queryFilters.misc_filters or { filters = {} }
Expand All @@ -95,7 +92,7 @@ local function buildURL(item, slotName, controls, modEntries, defenceEntries, is

-- Base type filter
if controls.baseTypeCheck and controls.baseTypeCheck.state then
queryTable.query.type = item.baseName
queryTable.type = item.baseName
end

-- Item level filter
Expand Down Expand Up @@ -165,21 +162,21 @@ local function buildURL(item, slotName, controls, modEntries, defenceEntries, is
if controls[prefix .. "Check"] and controls[prefix .. "Check"].state then
if #entry.tradeIds == 1 then
-- 1 id entries are added to the stat filters section
t_insert(queryTable.query.stats[1].filters, getFilter(entry.tradeIds[1]))
t_insert(queryTable.stats[1].filters, getFilter(entry.tradeIds[1]))
elseif #entry.tradeIds > 1 then
-- ambiguous entries are added as a separate count filter
local countFilter = { type = "count", value = { min = 1 }, filters = {} }
for _, tradeId in ipairs(entry.tradeIds) do
t_insert(countFilter.filters, getFilter(tradeId))
end
t_insert(queryTable.query.stats, countFilter)
t_insert(queryTable.stats, countFilter)
end
end
end

-- Only include filters if we have any
if next(queryFilters) then
queryTable.query.filters = queryFilters
queryTable.filters = queryFilters
end

-- Build URL
Expand All @@ -191,8 +188,8 @@ local function buildURL(item, slotName, controls, modEntries, defenceEntries, is
local encodedLeague = league:gsub("[^%w%-%.%_%~]", function(c)
return string.format("%%%02X", string.byte(c))
end):gsub(" ", "+")
url = url .. "/" .. encodedLeague
url = url .. "?q=" .. urlEncode(queryJson)
url ..= "/" .. encodedLeague
url ..= "/" .. tradeHelpers.B64GzipEncode(queryJson)

return url
end
Expand Down
19 changes: 19 additions & 0 deletions src/Classes/TradeHelpers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -596,4 +596,23 @@ function M.newPlainNumericEdit(anchor, rect, init, prompt, limit, integer, chang
end
return ctrl
end


---@param str string String which will be encoded
---@return string? result The given string, gzipped and then Base64URL encoded
function M.B64GzipEncode(str)
local b64 = require("base64")
local deflated = Deflate(str, true)
if not deflated then return end
return b64.encode(deflated):gsub("%+", "-"):gsub("/", "_")
end

---@param str string String which will be decoded
---@return string? result The given string, Base64URL decoded and the ungzipped
function M.B64GzipDecode(str)
local b64 = require("base64")
local data = b64.decode(str:gsub("%-", "+"):gsub("_", "/"))
if not data then return end
return Inflate(data)
end
return M
19 changes: 14 additions & 5 deletions src/Classes/TradeQuery.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

local dkjson = require "dkjson"
local itemSlotHelper = LoadModule("Modules/ItemSlotHelper")
local tradeHelpers = require("Classes.TradeHelpers")

local get_time = os.time
local t_insert = table.insert
Expand Down Expand Up @@ -1116,7 +1117,8 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro
local nameColor = slotTbl.unique and colorCodes.UNIQUE or "^7"
controls["name" .. row_idx] = new("LabelControl"):LabelControl(top_pane_alignment_ref, { 0, row_idx * (row_height + row_vertical_padding), 135, row_height - 4 }, nameColor .. slotTbl.slotName)
controls["bestButton" .. row_idx] = new("ButtonControl"):ButtonControl({ "LEFT", controls["name" .. row_idx], "LEFT" }, { 135 + 8, 0, 80, row_height }, "Find best", function()
self.tradeQueryGenerator:RequestQuery(activeSlot, { slotTbl = slotTbl, controls = controls, row_idx = row_idx }, self.statSortSelectionList, function(context, query, errMsg)
---@param query table A table of filters
local function requestQueryHandler(context, query, errMsg)
if errMsg then
self:SetNotice(context.controls.pbNotice, colorCodes.NEGATIVE .. errMsg)
return
Expand All @@ -1125,11 +1127,17 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro
end
if main.api.authToken == nil then
local url = self.tradeQueryRequests:buildUrl(self.hostName .. "trade2/search", self.pbRealm, self.pbLeague)
url = url .. "?q=" .. urlEncode(query)
url = url .. "/" .. tradeHelpers.B64GzipEncode(dkjson.encode(query))
controls["uri"..context.row_idx]:SetText(url, true)
return
end
context.controls["priceButton"..context.row_idx].label = "Searching..."
-- the query that can be included in the url only contains the filters, which means we
-- need to modify the query slightly for the POST endpoint
query = dkjson.encode({
query = query,
sort = { ["statgroup.0"] = "desc" },
})
self.lastQueries[row_idx] = query
self.tradeQueryRequests:SearchWithQueryWeightAdjusted(self.pbRealm, self.pbLeague, query,
function(items, errMsg)
Expand Down Expand Up @@ -1185,7 +1193,8 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro
end
}
)
end)
end
self.tradeQueryGenerator:RequestQuery(activeSlot, { slotTbl = slotTbl, controls = controls, row_idx = row_idx }, self.statSortSelectionList, requestQueryHandler)
end)
controls["bestButton"..row_idx].shown = function() return not self.resultTbl[row_idx] end
controls["bestButton"..row_idx].enabled = function() return self.pbLeague end
Expand Down Expand Up @@ -1382,9 +1391,9 @@ you can add them, copy the link here, and press "Price Item" to evaluate the ite
exactQuery.query.filters.trade_filters.filters = exactQuery.query.filters.trade_filters.filters or { }
exactQuery.query.filters.trade_filters.filters.account = { input = itemResult.trader }

local exactQueryStr = dkjson.encode(exactQuery)
local exactQueryStr = dkjson.encode(exactQuery.query)

local encodedUrl = s_format("https://www.pathofexile.com/trade2/search/%s?q=%s", self.pbLeague, urlEncode(exactQueryStr))
local encodedUrl = s_format("https://www.pathofexile.com/trade2/search/%s/%s", self.pbLeague, tradeHelpers.B64GzipEncode(exactQueryStr))

Copy(encodedUrl)
OpenURL(encodedUrl)
Expand Down
65 changes: 30 additions & 35 deletions src/Classes/TradeQueryGenerator.lua
Original file line number Diff line number Diff line change
Expand Up @@ -969,38 +969,34 @@ function TradeQueryGeneratorClass:FinishQuery()
local requiredMods = self.calcContext.requiredMods or {}
local blockedMods = self.calcContext.blockedMods or {}
local queryTable = {
query = {
filters = self.calcContext.special.queryFilters or {
type_filters = {
filters = {
category = { option = self.calcContext.itemCategoryQueryStr },
rarity = { option = "nonunique" }
}
filters = self.calcContext.special.queryFilters or {
type_filters = {
filters = {
category = { option = self.calcContext.itemCategoryQueryStr },
rarity = { option = "nonunique" }
}
}
},
status = { option = selectedTradeType },
stats = {
{
type = "weight",
value = { min = minWeight },
filters = {},
},
status = { option = selectedTradeType },
stats = {
{
type = "weight",
value = { min = minWeight },
filters = {},
},
{
type = "and",
filters = {},
},
{
type = "not",
filters = {},
}
{
type = "and",
filters = {},
},
{
type = "not",
filters = {},
}
},
sort = { ["statgroup.0"] = "desc" },
engine = "new"
}
local weightGroup = queryTable.query.stats[1]
local andGroup = queryTable.query.stats[2]
local notGroup = queryTable.query.stats[3]
local weightGroup = queryTable.stats[1]
local andGroup = queryTable.stats[2]
local notGroup = queryTable.stats[3]
-- the trade site has a maximum complexity of 200 for each query. our baseline is 54 for the weighted sum group, 4 for the rarity filter plus category, and 4 for the and group
local complexityBudget = 200 - 54 - 4 - 4

Expand Down Expand Up @@ -1056,7 +1052,7 @@ function TradeQueryGeneratorClass:FinishQuery()

for k, v in pairs(self.calcContext.special.queryExtra or {}) do
complexityBudget = complexityBudget - 2
queryTable.query[k] = v
queryTable[k] = v
end

-- and filters specified by the user
Expand All @@ -1071,7 +1067,7 @@ function TradeQueryGeneratorClass:FinishQuery()
local options = self.calcContext.options
if not options.includeMirrored then
complexityBudget = complexityBudget - 3
queryTable.query.filters.misc_filters = {
queryTable.filters.misc_filters = {
disabled = false,
filters = {
mirrored = false,
Expand All @@ -1081,7 +1077,7 @@ function TradeQueryGeneratorClass:FinishQuery()

if options.maxPrice and options.maxPrice > 0 then
complexityBudget = complexityBudget - 3
queryTable.query.filters.trade_filters = {
queryTable.filters.trade_filters = {
filters = {
price = {
option = options.maxPriceType,
Expand All @@ -1093,11 +1089,11 @@ function TradeQueryGeneratorClass:FinishQuery()

if options.account then
complexityBudget = complexityBudget - 3
queryTable.query.filters.trade_filters.filters.account = { input = options.account }
queryTable.filters.trade_filters.filters.account = { input = options.account }
end
if options.maxLevel and options.maxLevel > 0 then
complexityBudget = complexityBudget - 3
queryTable.query.filters.req_filters = {
queryTable.filters.req_filters = {
disabled = false,
filters = {
lvl = {
Expand All @@ -1109,7 +1105,7 @@ function TradeQueryGeneratorClass:FinishQuery()

if options.sockets and options.sockets > 0 then
complexityBudget = complexityBudget - 3
queryTable.query.filters.equipment_filters = {
queryTable.filters.equipment_filters = {
disabled = false,
filters = {
rune_sockets = {
Expand All @@ -1134,8 +1130,7 @@ function TradeQueryGeneratorClass:FinishQuery()
errMsg = "Could not generate search, found no mods to search for"
end

local queryJson = dkjson.encode(queryTable)
self.requesterCallback(self.requesterContext, queryJson, errMsg)
self.requesterCallback(self.requesterContext, queryTable, errMsg)

-- Close blocker popup
main:ClosePopup()
Expand Down
Loading
Loading