892 lines
29 KiB
Lua
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 |