diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 8d54637e9..8bbf4da16 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -27,6 +27,7 @@ local TradeQueryClass = newClass("TradeQuery", function(self, itemsTab) self.resultTbl = { } self.sortedResultTbl = { } self.itemIndexTbl = { } + self.queryIdTbl = { } -- tooltip acceleration tables self.onlyWeightedBaseOutput = { } self.lastComparedWeightList = { } @@ -359,6 +360,20 @@ Highest Weight - Displays the order retrieved from trade]] -- self:PullPoENinjaCurrencyConversion(self.pbLeague) end) self.controls.pbNotice = new("LabelControl", {"BOTTOMRIGHT", nil, "BOTTOMRIGHT"}, {-row_height - pane_margins_vertical - row_vertical_padding, -pane_margins_vertical - row_height - row_vertical_padding, 300, row_height}, "") + + -- Add Trade Mode dropdown to the bottom right + self.tradeModeList = { + "Instant Buyout and In Person Trade", + "Instant Buyout Only", + "In Person Trade Only", + "Any" + } + self.pbTradeModeSelectionIndex = self.pbTradeModeSelectionIndex or 3 + self.controls.tradeModeSelection = new("DropDownControl", {"BOTTOMRIGHT", nil, "BOTTOMRIGHT"}, {-pane_margins_horizontal, -pane_margins_vertical, 220, row_height}, self.tradeModeList, function(index, value) + self.pbTradeModeSelectionIndex = index + end) + self.controls.tradeModeSelection:SetSel(self.pbTradeModeSelectionIndex) + self.controls.tradeModeSelection.enableDroppedWidth = true -- Realm selection self.controls.realmLabel = new("LabelControl", {"LEFT", self.controls.setSelect, "RIGHT"}, {18, 0, 20, row_height - 4}, "^7Realm:") @@ -486,6 +501,7 @@ Highest Weight - Displays the order retrieved from trade]] end end end + self.controls.fullPrice = new("LabelControl", {"BOTTOM", nil, "BOTTOM"}, {0, -row_height - pane_margins_vertical - row_vertical_padding, pane_width - 2 * pane_margins_horizontal, row_height}, "") self.controls.close = new("ButtonControl", {"BOTTOM", nil, "BOTTOM"}, {0, -pane_margins_vertical, 90, row_height}, "Done", function() main:ClosePopup() @@ -844,7 +860,7 @@ 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", top_pane_alignment_ref, {0, row_idx*(row_height + row_vertical_padding), 100, row_height - 4}, nameColor..slotTbl.slotName) controls["bestButton"..row_idx] = new("ButtonControl", { "LEFT", controls["name"..row_idx], "LEFT"}, {100 + 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) + self.tradeQueryGenerator:RequestQuery(activeSlot, { slotTbl = slotTbl, controls = controls, row_idx = row_idx }, self.statSortSelectionList, self.pbTradeModeSelectionIndex, function(context, query, errMsg) if errMsg then self:SetNotice(context.controls.pbNotice, colorCodes.NEGATIVE .. errMsg) return @@ -873,6 +889,7 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro end, { callbackQueryId = function(queryId) + self.queryIdTbl[context.row_idx] = queryId local url = self.tradeQueryRequests:buildUrl(self.hostName .. "trade2/search", self.pbRealm, self.pbLeague, queryId) controls["uri"..context.row_idx]:SetText(url, true) end @@ -922,7 +939,12 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro self:UpdateControlsWithItems(row_idx) end controls["priceButton"..row_idx].label = "Price Item" - end) + end, + { + callbackQueryId = function(queryId) + self.queryIdTbl[row_idx] = queryId + end + }) end) controls["priceButton"..row_idx].enabled = function() local poesessidAvailable = main.POESESSID and main.POESESSID ~= "" @@ -1001,22 +1023,53 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro end -- Whisper so we can copy to clipboard controls["whisperButton"..row_idx] = new("ButtonControl", { "TOPLEFT", controls["importButton"..row_idx], "TOPRIGHT"}, {8, 0, 185, row_height}, function() - return self.totalPrice[row_idx] and "Whisper for " .. self.totalPrice[row_idx].amount .. " " .. self.totalPrice[row_idx].currency or "Whisper" + if not self.itemIndexTbl[row_idx] then return "Whisper" end + local result = self.resultTbl[row_idx][self.itemIndexTbl[row_idx]] + local priceStr = self.totalPrice[row_idx] and " for " .. self.totalPrice[row_idx].amount .. " " .. self.totalPrice[row_idx].currency or "" + if result.hideout_token then + return "Teleport" .. priceStr + elseif result.whisper_token then + return "Whisper" .. priceStr + else + return "Copy Whisper" .. priceStr + end end, function() - Copy(self.resultTbl[row_idx][self.itemIndexTbl[row_idx]].whisper) - end) - controls["whisperButton"..row_idx].enabled = function() - return self.itemIndexTbl[row_idx] and self.resultTbl[row_idx][self.itemIndexTbl[row_idx]].whisper ~= nil - end - controls["whisperButton"..row_idx].tooltipFunc = function(tooltip) - tooltip:Clear() - if self.itemIndexTbl[row_idx] and self.resultTbl[row_idx][self.itemIndexTbl[row_idx]].item_string then - tooltip.center = true - tooltip:AddLine(16, "Copies the item purchase whisper to the clipboard") + local result = self.resultTbl[row_idx][self.itemIndexTbl[row_idx]] + local token = result.hideout_token or result.whisper_token + + if token then + local queryId = self.queryIdTbl and self.queryIdTbl[row_idx] + local referrerUrl = queryId and self.tradeQueryRequests:buildUrl(self.hostName .. "trade2/search", self.pbRealm, self.pbLeague, queryId) + + if not referrerUrl then + ConPrintf("Error: Could not construct referrer URL for whisper.") + if result.whisper then Copy(result.whisper) end + return + end + + self.tradeQueryRequests:SendWhisper(token, referrerUrl, function(response, errMsg) + local action = result.hideout_token and "Teleport" or "Whisper" + if errMsg or (response and response.error) then + local errorMsgStr = "Error: " .. (errMsg or (response.error and response.error.message) or "Unknown error") + ConPrintf("Action '%s' failed: %s", action, errorMsgStr) + self:SetNotice(self.controls.pbNotice, errorMsgStr) + if result.whisper then + ConPrintf("Falling back to clipboard copy.") + Copy(result.whisper) + end + else + ConPrintf("'%s' action successful!", action) + self:SetNotice(self.controls.pbNotice, action .. " sent!") + end + end) + elseif result.whisper then + ConPrintf("No token found. Falling back to clipboard copy.") + Copy(result.whisper) + else + ConPrintf("No token and no whisper text found. Cannot perform action.") end - end + end) end - -- Method to update the Total Price string sum of all items function TradeQueryClass:GetTotalPriceString() local text = "" diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index b9ce06109..084503e69 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -816,6 +816,9 @@ function TradeQueryGeneratorClass:FinishQuery() -- This Stat diff value will generally be higher than the weighted sum of the same item, because the stats are all applied at once and can thus multiply off each other. -- So apply a modifier to get a reasonable min and hopefully approximate that the query will start out with small upgrades. local minWeight = megalomaniacSpecialMinWeight or currentStatDiff * 0.5 + local tradeModeMap = { "available", "securable", "online", "any" } + local tradeMode = tradeModeMap[self.calcContext.options.tradeModeIndex] or "online" + -- Generate trade query str and open in browser local filters = 0 @@ -829,7 +832,7 @@ function TradeQueryGeneratorClass:FinishQuery() } } }, - status = { option = "online" }, + status = { option = tradeMode }, stats = { { type = "weight", @@ -910,7 +913,7 @@ function TradeQueryGeneratorClass:FinishQuery() main:ClosePopup() end -function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callback) +function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, tradeModeIndex, callback) self.requesterCallback = callback self.requesterContext = context @@ -1029,6 +1032,7 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb options.sockets = tonumber(controls.sockets.buf) end options.statWeights = statWeights + options.tradeModeIndex = tradeModeIndex self:StartQuery(slot, options) end) diff --git a/src/Classes/TradeQueryRateLimiter.lua b/src/Classes/TradeQueryRateLimiter.lua index 28960762c..de84d3004 100644 --- a/src/Classes/TradeQueryRateLimiter.lua +++ b/src/Classes/TradeQueryRateLimiter.lua @@ -42,7 +42,8 @@ local TradeQueryRateLimiterClass = newClass("TradeQueryRateLimiter", function(se -- convenient name lookup, can be extended self.policyNames = { ["search"] = "trade-search-request-limit", - ["fetch"] = "trade-fetch-request-limit" + ["fetch"] = "trade-fetch-request-limit", + ["whisper"] = "trade-whisper-request-limit" } self.delayCache = {} self.requestId = 0 @@ -53,6 +54,7 @@ local TradeQueryRateLimiterClass = newClass("TradeQueryRateLimiter", function(se self.pendingRequests = { ["trade-search-request-limit"] = {}, ["trade-fetch-request-limit"] = {}, + ["trade-whisper-request-limit"] = {}, ["character-list-request-limit-poe2"] = {}, ["character-request-limit-poe2"] = {} } diff --git a/src/Classes/TradeQueryRequests.lua b/src/Classes/TradeQueryRequests.lua index 92f4f23d8..eb553ea79 100644 --- a/src/Classes/TradeQueryRequests.lua +++ b/src/Classes/TradeQueryRequests.lua @@ -14,6 +14,7 @@ local TradeQueryRequestsClass = newClass("TradeQueryRequests", function(self, ra self.requestQueue = { ["search"] = {}, ["fetch"] = {}, + ["whisper"] = {}, } self.hostName = "https://www.pathofexile.com/" end) @@ -30,13 +31,15 @@ function TradeQueryRequestsClass:ProcessQueue() local requestId = self.rateLimiter:InsertRequest(policy) local onComplete = function(response, errMsg) self.rateLimiter:FinishRequest(policy, requestId) - self.rateLimiter:UpdateFromHeader(response.header) - if response.header:match("HTTP/[%d%.]+ (%d+)") == "429" then + if response and response.header then + self.rateLimiter:UpdateFromHeader(response.header) + end + if response and response.header and response.header:match("HTTP/[%d%.]+ (%d+)") == "429" then table.insert(queue, 1, request) return end -- if limit rules don't return account then the POESESSID is invalid. - if response.header:match("X%-Rate%-Limit%-Rules: (.-)\n"):match("Account") == nil and main.POESESSID ~= "" then + if response and response.header and response.header:match("X%-Rate%-Limit%-Rules: (.-)\n") and response.header:match("X%-Rate%-Limit%-Rules: (.-)\n"):match("Account") == nil and main.POESESSID ~= "" then main.POESESSID = "" if errMsg then errMsg = errMsg .. "\nPOESESSID is invalid. Please Re-Log and reset" @@ -44,17 +47,23 @@ function TradeQueryRequestsClass:ProcessQueue() errMsg = "POESESSID is invalid. Please Re-Log and reset" end end - request.callback(response.body, errMsg, unpack(request.callbackParams or {})) + request.callback(response and response.body or nil, errMsg, unpack(request.callbackParams or {})) end - -- self:SendRequest(request.url , onComplete, {body = request.body, poesessid = main.POESESSID}) - local header = "Content-Type: application/json" + + local header = request.headers or "Content-Type: application/json\nUser-Agent: Path of Building Community" if main.POESESSID ~= "" then header = header .. "\nCookie: POESESSID=" .. main.POESESSID end - launch:DownloadPage(request.url, onComplete, { + + local downloadOptions = { header = header, - body = request.body, - }) + body = request.body + } + if request.body then + downloadOptions.post = "raw" + end + + launch:DownloadPage(request.url, onComplete, downloadOptions) else break end @@ -414,6 +423,8 @@ function TradeQueryRequestsClass:FetchResultBlock(url, callback) currency = trade_entry.listing.price.currency, item_string = table.concat(rawLines, "\n"), whisper = trade_entry.listing.whisper, + whisper_token = trade_entry.listing.whisper_token, + hideout_token = trade_entry.listing.hideout_token, weight = trade_entry.item.pseudoMods and trade_entry.item.pseudoMods[1]:match("Sum: (.+)") or "0", id = trade_entry.id }) @@ -525,3 +536,40 @@ function TradeQueryRequestsClass:buildUrl(root, realm, league, queryId) end return result end + +---@param callback fun(items:table, errMsg:string) +function TradeQueryRequestsClass:SendWhisper(token, refererUrl, callback) + ConPrintf("Attempting to send whisper with token: " .. tostring(token)) + + -- Manually construct the JSON string to ensure a space after the colon + local requestBody = '{"token": "' .. token .. '"}' + + -- Construct the full headers required by the whisper API + local headers = { + "Content-Type: application/json", + "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36", + "X-Requested-With: XMLHttpRequest" + } + if refererUrl then + table.insert(headers, "Referer: " .. refererUrl) + end + + table.insert(self.requestQueue["whisper"], { + url = self.hostName .. "api/trade2/whisper", + body = requestBody, + headers = table.concat(headers, "\n"), + callback = function(responseBody, errMsg) + ConPrintf("Whisper API Response Body: " .. tostring(responseBody)) + ConPrintf("Whisper API Error Message: " .. tostring(errMsg)) + if errMsg then + return callback(nil, errMsg) + end + local response, jsonErr = dkjson.decode(responseBody) + if not response then + errMsg = "Failed to decode Whisper JSON response: " .. (jsonErr or "Empty response") + return callback(nil, errMsg) + end + callback(response, errMsg) + end, + }) +end \ No newline at end of file