Files
coa-tsm/TradeSkillMaster/Libs/LibAuctionScan-1.0/LibAuctionScan-1.0.lua
T
Andrew6810 f3e579cb57 init
2022-11-05 21:19:42 -07:00

892 lines
29 KiB
Lua

local MAJOR, MINOR = "LibAuctionScan-1.0", 2
local lib = LibStub:NewLibrary(MAJOR, MINOR)
if not lib then return end
lib.mixinTargets = lib.mixinTargets or {}
local mixins = {"StartScan", "StopScan", "GetAuctionQueryInfo", "GetCommonAuctionQueryInfo", "GetPageProgress", "FindAuction", "StopFindScan", "NewAuctionItem", "SortAuctions", "StartGetAllScan", "IsFindScanning"}
function lib:Embed(target)
for _,name in pairs(mixins) do
target[name] = lib[name]
end
lib.mixinTargets[target] = true
end
--[[-------------------------------------------------------------------------
LibAuctionScan Utility Functions
---------------------------------------------------------------------------]]
local function GetSafeItemInfo(link)
if type(link) ~= "string" then return end
-- if strmatch(link, "battlepet:") then
-- local _, speciesID, level, quality, health, power, speed, petID = strsplit(":", link)
-- if not speciesID then return end
-- level, quality, health, power, speed, petID = level or 0, quality or 0, health or 0, power or 0, speed or 0, petID or "0"
-- local name, texture = C_PetJournal.GetPetInfoBySpeciesID(speciesID)
-- level, quality = tonumber(level), tonumber(quality)
-- petID = strsub(petID, 1, (strfind(petID, "|") or #petID)-1)
-- link = ITEM_QUALITY_COLORS[quality].hex.."|Hbattlepet:"..speciesID..":"..level..":"..quality..":"..health..":"..power..":"..speed..":"..petID.."|h["..name.."]|h|r"
-- local minLvl, iType, _, stackSize, _, _, vendorPrice = select(5, GetItemInfo(82800))
-- local subType, equipLoc = 0, ""
-- return name, link, quality, level, minLvl, iType, subType, stackSize, equipLoc, texture, vendorPrice
-- elseif strmatch(link, "item:") then
if strmatch(link, "item:") then
return GetItemInfo(link)
end
end
local BASE_DELAY = 0.10 -- time to delay for before trying to scan a page again when it isn't fully loaded
-- Converts an itemLink into an itemString
local function GetItemString(itemLink)
if type(itemLink) ~= "string" and type(itemLink) ~= "number" then return end
itemLink = select(2, GetSafeItemInfo(itemLink)) or itemLink
-- it's an itemId and we couldn't get the itemLink so guess
if tonumber(itemLink) then
return "item:"..itemLink..":0:0:0:0:0:0"
end
local itemInfo = {strfind(itemLink, "|?c?f?f?(%x*)|?H?([^:]*):?(%d+):?(%d*):?(%d*):?(%d*):?(%d*):?(%d*):?(%-?%d*):?(%-?%d*):?(%-?%d*):?(%d*)|?h?%[?([^%[%]]*)%]?|?h?|?r?")}
if not itemInfo[11] then return end
itemInfo[11] = tonumber(itemInfo[11]) or 0
return table.concat(itemInfo, ":", 4, 11)
end
local delays = {}
-- Cancels a time delay
local function CancelFrame(label)
local delayFrame
for i, frame in pairs(delays) do
if frame.label == label then
delayFrame = frame
end
end
if delayFrame then
delayFrame:Hide()
delayFrame.label = nil
delayFrame.inUse = false
delayFrame.validate = nil
delayFrame.timeLeft = nil
delayFrame:SetScript("OnUpdate", nil)
end
end
-- Creates a time delay
-- the callback is called after the specified amount of time has passed
local function CreateTimeDelay(label, duration, callback)
if not (label and type(duration) == "number" and type(callback) == "function") then return end
local frameNum
for i, frame in pairs(delays) do
if frame.label == label then return end
if not frame.inUse then
frameNum = i
end
end
if not frameNum then
local delay = CreateFrame("Frame")
delay:Hide()
tinsert(delays, delay)
frameNum = #delays
end
local frame = delays[frameNum]
frame.inUse = true
frame.label = label
frame.timeLeft = duration
frame:SetScript("OnUpdate", function(self, elapsed)
self.timeLeft = self.timeLeft - elapsed
if self.timeLeft <= 0 then
CancelFrame(self.label)
callback()
end
end)
frame:Show()
end
local function AuctionDataIsBad(temp, resolveSeller)
local shown = GetNumAuctionItems("list")
local badData = false
for i=1, shown do
-- checks to make sure all the data has been sent to the client
-- if not, the data is bad and we'll wait / try again
local count, _, _, _, _, _, _, buyout, _, _, seller = select(3, GetAuctionItemInfo("list", i))
local itemString = GetItemString(GetAuctionItemLink("list", i))
temp[i] = {itemString=itemString, index=i}
if not (itemString and buyout and count and (seller or not resolveSeller)) then
badData = true
end
end
return badData
end
--[[-------------------------------------------------------------------------
LibAuctionScan Scanning Functions (lib:StartScan, lib:StopScan)
---------------------------------------------------------------------------]]
do
lib.scanFrame = lib.scanFrame or CreateFrame("Frame")
local private = {}
local status = {page=0, retries=0, timeDelay=0, AH=false, filterlist = {}}
local function eventHandler(frame, event)
if event == "AUCTION_HOUSE_SHOW" then
-- auction house was opened
status.AH = true
elseif event == "AUCTION_HOUSE_CLOSED" then
-- auction house was closed, make sure all scanning is stopped
frame:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
status.AH = false
if status.isScanning then -- stop scanning if we were scanning (pass true to specify it was interrupted)
private:StopScanning(true)
end
elseif event == "AUCTION_ITEM_LIST_UPDATE" then
-- gets called whenever the AH window is updated (something is shown in the results section)
frame:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
if not status.isScanning then return end
CancelFrame("updateDelay")
-- now that our query was successful, we can get our data
private:ScanAuctions()
end
end
lib.scanFrame:SetScript("OnEvent", eventHandler)
lib.scanFrame:RegisterEvent("AUCTION_HOUSE_CLOSED")
lib.scanFrame:RegisterEvent("AUCTION_HOUSE_SHOW")
local function DoCallback(...)
if type(status.callbackHandler) == "function" then
status.callbackHandler(...)
end
end
-- gets the number of pages a certain query results in
-- used to determine whether combined filters should be split up
local function GetNumPages(filter, callbackFunc)
lib:StopScan()
if not status.AH then
return
elseif not CanSendAuctionQuery() then
local delay = CreateFrame("Frame")
delay:SetScript("OnUpdate", function(self)
if CanSendAuctionQuery() then
self:Hide()
if status.isScanning == nil then return end
GetNumPages(unpack(self.params))
end
end)
delay:Show()
delay.params = {filter, callbackFunc}
return
end
local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("AUCTION_ITEM_LIST_UPDATE")
eventFrame:SetScript("OnEvent", function(self)
local _, total = GetNumAuctionItems("list")
local totalPages = math.ceil(total / 50)
self:UnregisterAllEvents()
self:Hide()
if status.isScanning ~= false then return end
callbackFunc(totalPages)
end)
eventFrame:Show()
QueryAuctionItems(filter.name, filter.minLevel, filter.maxLevel, filter.invType, filter.class, filter.subClass, 0, filter.usable, filter.quality)
end
-- splits the combined filter into individual item filters
local function SplitCurrentFilterItems()
local newFilters = {}
for _, item in ipairs(status.filter.arg) do
local name = GetSafeItemInfo(item)
local temp = CopyTable(status.filter)
temp.arg = item
temp.isCombinedFilter = nil
temp.name = name
tinsert(newFilters, temp)
end
tremove(status.filterList, 1)
for _, filter in ipairs(newFilters) do
tinsert(status.filterList, 1, filter)
end
status.filter = status.filterList[1]
DoCallback("UPDATE_TOTAL_FILTERS", #status.filterList)
end
local function SortAuctionsAscending(header)
SortAuctionItems("list", header)
if IsAuctionSortReversed("list", header) then
SortAuctionItems("list", header)
end
end
local function IsDuplicatePage()
if not private.pageTemp or GetNumAuctionItems("list") == 0 then return false end
local numLinks, prevLink = 0, nil
for i=1, GetNumAuctionItems("list") do
local _, _, count, _, _, _, _, minBid, minInc, buyout, bid, _, seller = GetAuctionItemInfo("list", i)
local link = GetAuctionItemLink("list", i)
local temp = private.pageTemp[i]
if not prevLink then
prevLink = link
elseif prevLink ~= link then
prevLink = link
numLinks = numLinks + 1
end
if not temp or temp.count ~= count or temp.minBid ~= minBid or temp.minInc ~= minInc or temp.buyout ~= buyout or temp.bid ~= bid or temp.seller ~= seller or temp.link ~= link then
return false
end
end
if numLinks > 1 and private.pageTemp.shown == GetNumAuctionItems("list") then
return false
end
return true
end
local function PopulatePageTemp()
local shown = GetNumAuctionItems("list")
private.pageTemp = {numShown=shown}
for i=1, shown do
-- checks to make sure all the data has been sent to the client
-- if not, the data is bad and we'll wait / try again
local _, _, count, _, _, _, _, minBid, minInc, buyout, bid, _, seller = GetAuctionItemInfo("list", i)
local link = GetAuctionItemLink("list", i)
private.pageTemp[i] = {count=count, minBid=minBid, minInc=minInc, buyout=buyout, bid=bid, seller=seller, link=link}
end
end
-- Starts a scan of the auction house.
-- scanQueue - A list of queries. Each entry represents a unique set of QueryAuctionItem paramters:
-- name, minLevel, maxLevel, invType, class, subClass, usable, quality
-- options - special options for the scan (all optional)
-- sellerResolution - resolve seller names
-- missingSellerName - allows a missing seller (only applies if sellerResolution is set) and sets the name to this string
-- passiveRequest - if there is a scan going on, ignore this request (otherwise stop it and run this request)
-- maxRetries - sets the max number of retries, 3 by default
-- retryDelay - how long the scan should wait before retrying the query, 4 sec by default
function lib:StartScan(scanQueue, callbackHandler, scanOptions)
lib:StopFindScan()
if status.isScanning then
if scanOptions.passiveRequest then
return -4
else
lib:StopScan()
end
end
if not status.AH then
return -1 -- the auction house isn't open (return code -1)
elseif type(scanQueue) ~= "table" or #scanQueue == 0 then
return -2 -- the scan queue is empty or not a table (return code -2)
elseif not CanSendAuctionQuery() then
local delay = CreateFrame("Frame")
delay:SetScript("OnUpdate", function(self)
if CanSendAuctionQuery() then
self:Hide()
lib:StartScan(unpack(self.params))
end
end)
delay:Show()
delay.params = {scanQueue, callbackHandler, scanOptions}
return 0 -- the query will start as soon as it can but did not start immediately (return code 0)
end
local filterList = {}
for i=1, #scanQueue do
if type(scanQueue[i]) ~= "table" then
local entry = {}
local isNum = tonumber(scanQueue[i]) and true
local isItemIDFilter
local itemString = GetItemString(scanQueue[i])
if type(scanQueue[i]) == "string" and #{(":"):split(scanQueue[i])} == 2 then
itemString = scanQueue[i]
isItemIDFilter = true
end
if itemString then
scanQueue[i] = lib:GetAuctionQueryInfo(itemString)
if scanQueue[i] then
scanQueue[i].exactOnly = not isNum
scanQueue[i].arg = itemString
scanQueue[i].scanTemp = {didCallback={}}
scanQueue[i].isItemIDFilter = scanQueue[i].isItemIDFilter or isItemIDFilter
tinsert(filterList, scanQueue[i])
end
else
return -3 -- the scan queue contained invalid entries (return code -3)
end
else
scanQueue[i].scanTemp = {didCallback={}}
tinsert(filterList, scanQueue[i])
end
end
if #filterList == 0 then
return -2
end
-- set defaults
scanOptions.maxRetries = scanOptions.maxRetries or 3
scanOptions.retryDelay = scanOptions.retryDelay or 2
-- initialize all the values of the status table
-- filter = current category being scanned for {class, subClass, invSlot}
-- filterList = queue of categories to scan for
status.data = {} -- the data we've scanned so far
status.page = 0 -- what page we are currently scanning (starts at 0)
status.retries = 0 -- how many times we've done a hard retry so far
status.hardRetry = nil -- if a page hasn't loaded after we've tried a delay, we'll do a hard retry and re-send the query
status.filterList = filterList -- list of filters
status.filter = filterList[1] -- current filter
status.isScanning = true -- used to prevent functions from running when we're not supposed to be scanning
status.callbackHandler = callbackHandler
status.options = scanOptions -- any special options for this scan
--starts scanning
private:SendQuery()
return 1 -- scan started successfully (return code 1)
end
-- sends a query to the AH frame once it is ready to be queried (uses frame as a delay)
function private:SendQuery()
if not status.isScanning then return end
if CanSendAuctionQuery() then
-- stop delay timer
CancelFrame("queryDelay")
if status.page == 0 and status.filter.isCombinedFilter and type(status.filter.arg) == "table" and not status.filter.scanTemp.checkedPages then
status.filter.scanTemp.checkedPages = true
status.isScanning = false
GetNumPages(status.filter, function(numPages)
if status.isScanning == nil then return end
if numPages > #status.filter.arg then
SplitCurrentFilterItems()
end
status.isScanning = true
private:SendQuery()
end)
return
end
-- Query the auction house (then waits for AUCTION_ITEM_LIST_UPDATE to fire)
lib.scanFrame:RegisterEvent("AUCTION_ITEM_LIST_UPDATE")
QueryAuctionItems(status.filter.name, status.filter.minLevel, status.filter.maxLevel, status.filter.invType, status.filter.class, status.filter.subClass, status.page, status.filter.usable, status.filter.quality)
else
-- run delay timer then try again to scan
CreateTimeDelay("queryDelay", 0.05, private.SendQuery)
end
end
--scans the currently shown page of auctions and collects all the data
function private:ScanAuctions()
if not status.isScanning then return end
local shown, total = GetNumAuctionItems("list")
local totalPages = math.ceil(total / 50)
local temp = {}
local dataIsBad = AuctionDataIsBad(temp, status.options.sellerResolution)
-- check that we have good data
if dataIsBad or IsDuplicatePage() then
if status.retries < status.options.maxRetries then
if status.hardRetry then
-- Hard retry
-- re-sends the entire query
status.retries = status.retries + 1
status.timeDelay = 0
status.hardRetry = nil
private:SendQuery()
else
-- Soft retry
-- runs a delay and then tries to scan the query again
status.timeDelay = status.timeDelay + BASE_DELAY
CreateTimeDelay("updateDelay", BASE_DELAY, private.ScanAuctions)
-- If after 2 seconds of retrying we still don't have data, will go and requery to try and solve the issue
-- if we still don't have data, we try to scan it anyway and move on.
if status.timeDelay >= status.options.retryDelay then
status.hardRetry = true
end
end
return
elseif dataIsBad and status.retries == status.options.maxRetries and status.options.sellerResolution and not status.options.missingSellerName then
-- give up
DoCallback("SCAN_TIMEOUT", status.filter)
status.hardRetry = nil
status.retries = 0
status.timeDelay = 0
private:RemoveCurrentFilter()
return
end
end
status.hardRetry = nil
status.retries = 0
status.timeDelay = 0
DoCallback("SCAN_STATUS_UPDATE", status.page+1, totalPages, #status.filterList)
PopulatePageTemp()
-- now that we know our query is good, time to verify and then store our data
for _, v in ipairs(temp) do
private:AddAuctionRecord(v.index)
end
-- This query has more pages to scan
-- increment the page # and send the new query
if totalPages > (status.page + 1) then
status.page = status.page + 1
private:SendQuery()
return
end
DoCallback("QUERY_FINISHED", {filter=status.filter, data=status.data, left=#status.filterList})
-- done with this filter so remove it
private:RemoveCurrentFilter()
end
-- called when we are done with the current filter
function private:RemoveCurrentFilter()
-- Removes the current filter from the filterList as we are done scanning for that item
for i=1, #(status.filterList) do
if status.filterList[i] == status.filter then
tremove(status.filterList, i)
break
end
end
-- Query the next filter if we have one
if status.filterList[1] then
status.filter = status.filterList[1]
status.page = 0
private:SendQuery()
return
end
-- we are done scanning!
private:StopScanning()
end
-- Add a new record to the status.data table
function private:AddAuctionRecord(index)
local name, texture, count, _, _, _, _, minBid, minIncrement, buyout, bid, highBidder, seller = GetAuctionItemInfo("list", index)
local timeLeft = GetAuctionItemTimeLeft("list", index)
local link = GetAuctionItemLink("list", index)
local itemString = GetItemString(link)
if not itemString then return end
if status.filter.isItemIDFilter then
local stringType, itemID = (":"):split(itemString)
itemString = stringType..":"..itemID
end
-- Create a new entry in the table
if not status.data[itemString] then
status.data[itemString] = lib:NewAuctionItem()
status.data[itemString]:SetItemLink(link)
status.data[itemString]:SetTexture(texture)
end
status.data[itemString]:AddAuctionRecord(count, minBid, minIncrement, buyout, bid, highBidder, seller or status.options.missingSellerName, timeLeft)
end
-- stops the scan when we are finished scanning, it was interrupted, or somebody stopped it
function private:StopScanning(interrupted)
if not status.isScanning then return end
if interrupted then
-- fires if the scan was interrupted
DoCallback("SCAN_INTERRUPTED")
else
-- fires if the scan completed sucessfully
DoCallback("SCAN_COMPLETE", status.data)
end
-- cancel any delays that might still be running
CancelFrame("queryDelay")
CancelFrame("updateDelay")
status.isScanning = nil
private.pageTemp = nil
return true
end
-- gets the current page progress
function lib:GetPageProgress()
local shown, total = GetNumAuctionItems("list")
local totalPages = ceil(total / 50)
return status.page + 1, totalPages
end
-- API for stopping the scan
-- returns true/false if we were/weren't actually scanning
function lib:StopScan()
lib:StopFindScan()
return private:StopScanning(true)
end
do
--[[-------------------------------------------------------------------------
GetAll Scan Code
---------------------------------------------------------------------------]]
local function GetAllScanFrameUpdate(self)
if not AuctionFrame:IsVisible() then self:Hide() end
-- get data for at most 200 auctions per update to avoid excessive lag
for i=1, 200 do
local link = GetAuctionItemLink("list", self.num)
local _, _, quantity, _, _, _, _, _, _, buyout = GetAuctionItemInfo("list", self.num)
if self.tries == 0 or (link and quantity and buyout) then
self.num = self.num + 1
self.tries = 3
if link then
private:AddAuctionRecord(self.num)
end
DoCallback("GETALL_UPDATE", self.num, self.numShown)
-- check if we are done scanning or not
if self.num == self.numShown then
-- bug with getall scan only being able to return a max of 42554 auctions
if self.num ~= self.totalNum then
DoCallback("GETALL_BUG")
end
self:Hide()
private:StopScanning()
break
end
else
self.tries = self.tries - 1
break
end
end
end
local scanFrame = CreateFrame("Frame")
scanFrame:Hide()
scanFrame:SetScript("OnUpdate", GetAllScanFrameUpdate)
-- every half second we check to see if there are more than 50 auctions
-- if so, we show the scan frame and start scanning the data
local function DataAvailableFrameUpdate(self, elapsed)
if not AuctionFrame:IsVisible() then self:Hide() end
self.delay = self.delay - elapsed
self.totalDelay = self.totalDelay - elapsed
DoCallback("GETALL_WAITING", 20-self.totalDelay)
if self.delay <= 0 then
if GetNumAuctionItems("list") > 50 then
-- data is ready to be scanned!
scanFrame.numShown, scanFrame.totalNum = GetNumAuctionItems("list")
self:Hide()
scanFrame:Show()
else
-- wait another half second
self.delay = 1
end
end
end
local dataAvailableFrame = CreateFrame("Frame")
dataAvailableFrame:Hide()
dataAvailableFrame:SetScript("OnUpdate", DataAvailableFrameUpdate)
function lib:StartGetAllScan(callbackHandler)
lib:StopScan()
if not status.AH then
return -1 -- the auction house isn't open (return code -1)
elseif not CanSendAuctionQuery() then
local delay = CreateFrame("Frame")
delay:SetScript("OnUpdate", function(self)
if CanSendAuctionQuery() then
self:Hide()
lib:StartGetAllScan(unpack(self.params))
end
end)
delay:Show()
delay.params = {callbackHandler}
return 0 -- the query will start as soon as it can but did not start immediately (return code 0)
elseif not select(2, CanSendAuctionQuery()) then
return -2 -- getall scan not ready
end
-- initialize all the values of the status table
status.options = {}
status.filter = {}
status.data = {} -- the data we've scanned so far
status.isScanning = "getAll" -- used to prevent functions from running when we're not supposed to be scanning
status.callbackHandler = callbackHandler
QueryAuctionItems("", 0, 0, 0, 0, 0, 0, 0, -1, true)
scanFrame.num = 0
scanFrame.tries = 3
dataAvailableFrame.totalDelay = 20
dataAvailableFrame.delay = 2
dataAvailableFrame:Show()
return 1 -- scan started successfully (return code 1)
end
end
end
--[[-------------------------------------------------------------------------
LibAuctionScan Finding Functions (lib:FindAuction, lib:StopFindScan)
---------------------------------------------------------------------------]]
do
lib.findFrame = lib.findFrame or CreateFrame("Frame")
local equipLocLookup = {
[INVTYPE_HEAD]=1, [INVTYPE_NECK]=2, [INVTYPE_SHOULDER]=3, [INVTYPE_BODY]=4, [INVTYPE_CHEST]=5,
[INVTYPE_WAIST]=6, [INVTYPE_LEGS]=7, [INVTYPE_FEET]=8, [INVTYPE_WRIST]=9, [INVTYPE_HAND]=10,
[INVTYPE_FINGER]=11, [INVTYPE_TRINKET]=12, [INVTYPE_CLOAK]=13, [INVTYPE_HOLDABLE]=14,
[INVTYPE_WEAPONMAINHAND]=15, [INVTYPE_ROBE]=16, [INVTYPE_TABARD]=17, [INVTYPE_BAG]=18,
[INVTYPE_2HWEAPON]=19, [INVTYPE_RANGED]=20, [INVTYPE_SHIELD]=21, [INVTYPE_WEAPON]=22
}
local private, status = {}, {}
local function eventHandler(frame, event)
if event == "AUCTION_HOUSE_SHOW" then
-- auction house was opened
status.AH = true
elseif event == "AUCTION_HOUSE_CLOSED" then
frame:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
status.AH = false
if status.isScanning then -- stop scanning if we were scanning (pass true to specify it was interrupted)
lib:StopFindScan()
end
elseif event == "AUCTION_ITEM_LIST_UPDATE" then
frame:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
if status.isScanning then
status.timeDelay = 0
CancelFrame("auctionFindScanDelay")
-- now that our query was successful we can get our data
private:ScanAuctions()
end
end
end
lib.findFrame:SetScript("OnEvent", eventHandler)
lib.findFrame:RegisterEvent("AUCTION_HOUSE_CLOSED")
lib.findFrame:RegisterEvent("AUCTION_HOUSE_SHOW")
function lib:GetAuctionQueryInfo(itemString)
local name, _, rarity, _, minLevel, class, subClass, _, equipLoc = GetSafeItemInfo(itemString)
if not name then return end
return {name=name, minLevel=minLevel, maxLevel=minLevel, invType=(equipLocLookup[equipLoc] or 0), class=class, subClass=subClass, quality=rarity}
end
function lib:GetCommonAuctionQueryInfo(items, nameFilter)
if not nameFilter or not items or #items == 0 then return end
local result = {name=nameFilter, minLevel=nil, maxLevel=nil, invType=-1, class=-1, subClass=-1, quality=4}
for _, itemString in ipairs(items) do
local itemFilters = lib:GetAuctionQueryInfo(itemString)
if not itemFilters or not strfind(strlower(itemFilters.name), nameFilter) then return end
if not result.minLevel or itemFilters.minLevel < result.minLevel then
result.minLevel = itemFilters.minLevel
end
if not result.maxLevel or itemFilters.maxLevel > result.maxLevel then
result.maxLevel = itemFilters.maxLevel
end
if result.invType == -1 then
result.invType = itemFilters.invType
elseif result.invType ~= itemFilters.invType then
result.invType = 0
end
if result.class == -1 then
result.class = itemFilters.class
elseif result.class ~= itemFilters.class then
result.class = 0
end
if result.subClass == -1 then
result.subClass = itemFilters.subClass
elseif result.subClass ~= itemFilters.subClass then
result.subClass = 0
end
if result.quality > itemFilters.quality then
result.quality = itemFilters.quality
end
end
return result
end
local function IsTargetAuction(index)
local itemID = Get
local itemString = GetItemString(GetAuctionItemLink("list", index))
local _, _, count, _, _, _, _, minBid, bidIncrement, buyout, bidAmount, _, seller, _, itemID = GetAuctionItemInfo("list", index)
local bid = bidAmount == 0 and minBid or bidAmount
local info = status.targetInfo
if type(info.itemString) == "number" then
itemString = itemID
end
return (not info.itemString or itemString == info.itemString) and (not info.count or count == info.count) and (not info.bid or bid == info.bid) and (not info.buyout or buyout == info.buyout) and (not info.seller or seller == info.seller)
end
-- valid targetInfo keys: itemString, count, bid, buyout, seller
function lib:FindAuction(callback, targetInfo)
if status.isScanning then lib:StopFindScan() end
local name, _, rarity, _, minLevel, class, subClass, _, equipLoc = GetSafeItemInfo(targetInfo.itemString)
status.query = {name=name, minLevel=minLevel, maxLevel=minLevel, invSlot=(equipLocLookup[equipLoc] or 0), class=class, subClass=subClass, rarity=rarity}
status.targetInfo = targetInfo
status.callback = callback
status.page = 0
status.isScanning = true
status.retries = 0
status.hardRetry = nil
-- check if the item is on the current page
for i=1, GetNumAuctionItems("list") do
if IsTargetAuction(i) then
lib:StopFindScan()
CreateTimeDelay("queryFoundDelay", 0.1, function() status.callback(i) end)
return
end
end
private:SendQuery()
end
-- sends a query to the AH frame once it is ready to be queried (uses frame as a delay)
function private:SendQuery()
if not status.isScanning then return end
if CanSendAuctionQuery() then
-- stop delay timer
CancelFrame("auctionFindQueryDelay")
-- query the auction house (then waits for AUCTION_ITEM_LIST_UPDATE to fire)
lib.findFrame:RegisterEvent("AUCTION_ITEM_LIST_UPDATE")
QueryAuctionItems(status.query.name, status.query.minLevel, status.query.maxLevel, status.query.invType, status.query.class, status.query.subClass, status.page, 0, status.query.rarity)
else
-- run delay timer then try again to scan
CreateTimeDelay("auctionFindQueryDelay", 0.05, function() private:SendQuery() end)
end
end
-- scans the currently shown page of auctions and collects all the data
function private:ScanAuctions()
if not status.isScanning then return end
-- collects data on the query:
-- # of auctions on current page
-- # of pages total
local shown, total = GetNumAuctionItems("list")
local totalPages = math.ceil(total / 50)
local temp = {}
-- Check for bad data
if status.retries < 3 then
if AuctionDataIsBad(temp) then
if status.hardRetry then
-- Hard retry
-- re-sends the entire query
status.retries = status.retries + 1
private:SendQuery()
else
-- Soft retry
-- runs a delay and then tries to scan the query again
status.timeDelay = status.timeDelay + BASE_DELAY
CreateTimeDelay("auctionFindScanDelay", BASE_DELAY, private.ScanAuctions)
-- If after 4 seconds of retrying we still don't have data, will go and requery to try and solve the issue
-- if we still don't have data, we try to scan it anyway and move on.
if status.timeDelay >= 4 then
status.hardRetry = true
status.retries = 0
end
end
return
end
end
status.hardRetry = nil
status.retries = 0
-- now that we know our query is good, time to verify and then store our data
for i=1, shown do
if IsTargetAuction(temp[i].index) then
lib:StopFindScan()
return status.callback(temp[i].index)
end
end
-- This query has more pages to scan
-- increment the page # and send the new query
if totalPages > (status.page + 1) then
status.page = status.page + 1
private:SendQuery()
return
end
-- we are done scanning!
lib:StopFindScan()
return status.callback()
end
-- stops the scan because it was either interrupted or it was completed successfully
function lib:StopFindScan()
lib.findFrame:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
status.isScanning = nil
CancelFrame("auctionFindQueryDelay")
CancelFrame("auctionFindScanDelay")
end
function lib:IsFindScanning()
return status.isScanning and CopyTable(status.targetInfo)
end
end
-- update the mixins
for target,_ in pairs(lib.mixinTargets) do
lib:Embed(target)
end