diff --git a/src/Classes/PoEAPI.lua b/src/Classes/PoEAPI.lua index 7a9031df05..70e4e878d1 100644 --- a/src/Classes/PoEAPI.lua +++ b/src/Classes/PoEAPI.lua @@ -236,3 +236,9 @@ function PoEAPIClass:DownloadCharacter(realm, name, callback) self:DownloadWithRateLimit("character-request-limit", "/character" .. (realm == "pc" and "" or "/" .. realm) .. "/" .. name, callback) end + +---@param realm string Realm to fetch the leagues for +---@param callback DownloadCallback +function PoEAPIClass:FetchLeagues(realm, callback) + self:DownloadWithRateLimit("league-request-limit", "/account/leagues" .. ((realm == "pc") and "" or ("/" .. realm)), callback) +end diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index d313d75f5e..6ce1264020 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -72,39 +72,6 @@ function TradeQueryClass:TradeQuery(itemsTab) end - --- Method to pull down and interpret available leagues from PoE -function TradeQueryClass:PullLeagueList() - launch:DownloadPage( - self.hostName .. "api/leagues?type=main&compact=1", - function(response, errMsg) - if errMsg then - self:SetNotice(self.controls.pbNotice, "Error: " .. tostring(errMsg)) - return "POE ERROR", "Error: "..errMsg - else - local json_data = dkjson.decode(response.body) - if not json_data then - self:SetNotice(self.controls.pbNotice, "Failed to Get PoE League List response") - return - end - table.sort(json_data, function(a, b) - if a.endAt == nil then return false end - if b.endAt == nil then return true end - return a.id < b.id - end) - self.itemsTab.leagueDropList = {} - for _, league_data in pairs(json_data) do - if not league_data.id:find("SSF") then - t_insert(self.itemsTab.leagueDropList,league_data.id) - end - end - self.controls.league:SetList(self.itemsTab.leagueDropList) - self.controls.league.selIndex = 1 - self.pbLeague = self.itemsTab.leagueDropList[self.controls.league.selIndex] - end - end) -end - --- @param currencyId string --- @param amount integer --- @return number? @@ -341,8 +308,10 @@ function TradeQueryClass:PriceItem() main:SaveSettings() TradeQueryClass:SetNotice(self.controls.pbNotice, "") + self:UpdateRealms() else self.loginStatus = colorCodes.WARNING.."Not authenticated" + self:UpdateRealms() end end) self.clickTime = os.time() @@ -355,11 +324,12 @@ function TradeQueryClass:PriceItem() main.tokenExpiry = nil main.api.tokenExpiry = nil main:SaveSettings() + self:UpdateRealms() end end) self.controls.tradeAuthButton.tooltipText = [[ The Trader feature supports two modes of operation depending on the authorization availability. -You can click this button to authorize PoB by logging in. +You can click this button to authorize PoB by logging in. The URL is also copied to your clipboard. ^2Session Mode^7 - Requires authorization on pathofexile.com. @@ -469,9 +439,17 @@ Highest Weight - Displays the order retrieved from trade]] local function setLeagueDropList() self.itemsTab.leagueDropList = copyTable(self.allLeagues[self.pbRealm]) self.controls.league:SetList(self.itemsTab.leagueDropList) + -- correct index as it might move when we fetch a new league list + local leagueIndex = self.pbLeagueIndex + for index, league in ipairs(self.itemsTab.leagueDropList) do + if league == self.pbLeague then + leagueIndex = index + break + end + end -- invalidate selIndex to trigger select function call in the SetSel self.controls.league.selIndex = nil - self.controls.league:SetSel(self.pbLeagueIndex) + self.controls.league:SetSel(leagueIndex) end if self.allLeagues[self.pbRealm] then setLeagueDropList() @@ -1275,46 +1253,46 @@ you can add them, copy the link here, and press "Price Item" to evaluate the ite end -- Whisper so we can copy to clipboard controls["whisperButton" .. row_idx] = new("ButtonControl"):ButtonControl({ "TOPLEFT", controls["importButton" .. row_idx], "TOPRIGHT" }, { 8, 0, 155, row_height }, function() - local itemResult = self.itemIndexTbl[row_idx] and self.resultTbl[row_idx][self.itemIndexTbl[row_idx]] + local itemResult = self.itemIndexTbl[row_idx] and self.resultTbl[row_idx][self.itemIndexTbl[row_idx]] - if not itemResult then return "" end + if not itemResult then return "" end - local price = self.totalPrice[row_idx] and - self.totalPrice[row_idx].amount .. " " .. self.totalPrice[row_idx].currency + local price = self.totalPrice[row_idx] and + self.totalPrice[row_idx].amount .. " " .. self.totalPrice[row_idx].currency - -- we also check the price type so we can prefer instant buyout over - -- whisper - if itemResult.whisper and (itemResult.priceType ~= "~b/o") then - return price and "Whisper for " .. price or "Whisper" - else - return price and "Search for " .. price or "Search" - end - - end, function() - local itemResult = self.itemIndexTbl[row_idx] and self.resultTbl[row_idx][self.itemIndexTbl[row_idx]] - if itemResult.whisper and (itemResult.priceType ~= "~b/o") then - Copy(itemResult.whisper) - else - local exactQuery = dkjson.decode(self.lastQueries[row_idx]) - -- use trade sum to get the specific item. both min and max - -- weight on site uses floats but only shows integer in the api - -- e.g. weight of 172.3 shows up as 172 in the api - exactQuery.query.stats[1].value = { min = floor(itemResult.weight, 1) - 1, max = round(itemResult.weight, 1) + 1 } - -- also apply trader name. this should make false positives - -- extremely unlikely. this doesn't seem to take up a filter slot - exactQuery.query.filters = exactQuery.query.filters or { } - exactQuery.query.filters.trade_filters = exactQuery.query.filters.trade_filters or { filters = { } } - 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 encodedUrl = s_format("https://www.pathofexile.com/trade/search/%s?q=%s", self.pbLeague, urlEncode(exactQueryStr)) - - Copy(encodedUrl) - OpenURL(encodedUrl) - end - end) + -- we also check the price type so we can prefer instant buyout over + -- whisper + if itemResult.whisper and (itemResult.priceType ~= "~b/o") then + return price and "Whisper for " .. price or "Whisper" + else + return price and "Search for " .. price or "Search" + end + end, function() + local itemResult = self.itemIndexTbl[row_idx] and self.resultTbl[row_idx][self.itemIndexTbl[row_idx]] + if itemResult.whisper and (itemResult.priceType ~= "~b/o") then + Copy(itemResult.whisper) + else + local exactQuery = dkjson.decode(self.lastQueries[row_idx]) + -- use trade sum to get the specific item. both min and max + -- weight on site uses floats but only shows integer in the api + -- e.g. weight of 172.3 shows up as 172 in the api + exactQuery.query.stats[1].value = { min = floor(itemResult.weight, 1) - 1, max = round(itemResult.weight, 1) + 1 } + -- also apply trader name. this should make false positives + -- extremely unlikely. this doesn't seem to take up a filter slot + exactQuery.query.filters = exactQuery.query.filters or {} + exactQuery.query.filters.trade_filters = exactQuery.query.filters.trade_filters or { filters = {} } + 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 realmPath = (self.pbRealm ~= "pc") and (self.pbRealm .. "/") or "" + local encodedUrl = s_format("https://www.pathofexile.com/trade/search/%s%s?q=%s", realmPath, urlEncode(self.pbLeague), urlEncode(exactQueryStr)) + + Copy(encodedUrl) + OpenURL(encodedUrl) + end + end) controls["whisperButton" .. row_idx].tooltipFunc = function(tooltip) tooltip:Clear() @@ -1386,29 +1364,19 @@ function TradeQueryClass:UpdateRealms() -- use trade leagues api to get trade leagues including private leagues is valid. self.allLeagues = {} - for _, realmId in pairs (self.realmIds) do - self.tradeQueryRequests:FetchLeagues(realmId, function(leagues, errMsg) + setRealmDropList() + + -- perform a generic search to make sure the authorization is valid. + if main.api.authToken then + self.tradeQueryRequests:PerformSearch("pc", "Standard", [[{"query":{"status":{"option":"online"},"stats":[{"type":"and","filters":[]}]},"sort":{"price":"asc"}}]], function(response, errMsg) if errMsg then - self:SetNotice(self.controls.pbNotice, "Using Fallback Error while fetching league list: "..errMsg) - end - for _, league in ipairs(leagues) do - if not self.allLeagues[realmId] then self.allLeagues[realmId] = {} end - t_insert(self.allLeagues[realmId], league) + -- a 403 here likely means that the user has an outdated scope + if errMsg == "Response code: 403" then + main.api:ResetDetails() + errMsg = errMsg .. "\nPlease re-authenticate" + end + self:SetNotice(self.controls.pbNotice, "Error: " .. tostring(errMsg)) end - setRealmDropList() - end) end - - -- perform a generic search to make sure the authorization is valid. - self.tradeQueryRequests:PerformSearch("pc", "Standard", [[{"query":{"status":{"option":"online"},"stats":[{"type":"and","filters":[]}]},"sort":{"price":"asc"}}]], function(response, errMsg) - if errMsg then - -- a 403 here likely means that the user has an outdated scope - if errMsg == "Response code: 403" then - main.api:ResetDetails() - errMsg = errMsg .. "\nPlease re-authenticate" - end - self:SetNotice(self.controls.pbNotice, "Error: " .. tostring(errMsg)) - end - end) end diff --git a/src/Classes/TradeQueryRateLimiter.lua b/src/Classes/TradeQueryRateLimiter.lua index f98c59f669..50dbbdbb54 100644 --- a/src/Classes/TradeQueryRateLimiter.lua +++ b/src/Classes/TradeQueryRateLimiter.lua @@ -56,7 +56,8 @@ function TradeQueryRateLimiterClass:TradeQueryRateLimiter() ["trade-search-request-limit"] = {}, ["trade-fetch-request-limit"] = {}, ["character-list-request-limit"] = {}, - ["character-request-limit"] = {} + ["character-request-limit"] = {}, + ["league-request-limit"] = {}, } return self end diff --git a/src/Classes/TradeQueryRequests.lua b/src/Classes/TradeQueryRequests.lua index 709d0c51bb..313ae021c0 100644 --- a/src/Classes/TradeQueryRequests.lua +++ b/src/Classes/TradeQueryRequests.lua @@ -466,8 +466,8 @@ end ---@param realm string ---@param callback fun(query:table, errMsg:string) function TradeQueryRequestsClass:FetchLeagues(realm, callback) - local header = "Authorization: Bearer " .. (main.api.authToken or "") - launch:DownloadPage( + local function fetchStatic() + launch:DownloadPage( self.hostName .. "api/trade/data/leagues", function(response, errMsg) if errMsg then @@ -489,9 +489,46 @@ function TradeQueryRequestsClass:FetchLeagues(realm, callback) end end callback(leagues, errMsg) - end, - {header = header} - ) + end + ) + end + if main.api.authToken then + main.api:FetchLeagues(realm, function(body, err) + -- fall back to static data on error + if not body or err then + ConPrintf("Failed to fetch oauth leagues: %s", err) + return fetchStatic() + end + if body.error then + local apiError = body.error + local errMsg = apiError or "Failed to parse trade leagues JSON" + if type(apiError) == "table" then + errMsg = apiError.message or (apiError.code and tostring(apiError.code)) or "Failed to parse trade leagues JSON" + end + ConPrintf("Failed to fetch oauth leagues: %s", errMsg) + return fetchStatic() + end + local leagues = {} + for _, value in pairs(body.leagues or {}) do + if value.rules then + -- filter out ssf leagues which might be present in the oauth query + for _, rule in ipairs(value.rules) do + if rule.id == "NoParties" then + goto skipLeague + end + end + end + table.insert(leagues, value.id) + ::skipLeague:: + end + if #leagues == 0 then + return fetchStatic() + end + callback(leagues, err) + end) + else + fetchStatic() + end end --- Build search and trade URLs with proper encoding