You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

533 lines
19 KiB

4 years ago
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster_Auctioning --
-- http://www.curse.com/addons/wow/tradeskillmaster_auctioning --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...)
local Cancel = TSM:NewModule("Cancel", "AceEvent-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster_Auctioning") -- loads the localization table
local cancelQueue, currentItem, tempIndexList = {}, {}, {}
local totalToCancel, totalCanceled, count = 0, 0, 0
local isScanning, GUI, isCancelAll, specialOptions
local itemsCancelled, itemsMissed = {}, {}
function Cancel:ValidateOperation(itemString, operation)
local _, itemLink = TSMAPI:GetSafeItemInfo(itemString)
local prices = TSM.Util:GetItemPrices(operation, itemString)
-- don't cancel this item if their settings are invalid
if not prices.minPrice then
if not TSM.db.global.disableInvalidMsg then
TSM:Printf(L["Did not cancel %s because your minimum price (%s) is invalid. Check your settings."], itemLink or itemString, operation.minPrice)
end
TSM.Log:AddLogRecord(itemString, "cancel", "Skip", "invalid")
elseif not prices.maxPrice then
if not TSM.db.global.disableInvalidMsg then
TSM:Printf(L["Did not cancel %s because your maximum price (%s) is invalid. Check your settings."], itemLink or itemString, operation.maxPrice)
end
TSM.Log:AddLogRecord(itemString, "cancel", "Skip", "invalid")
elseif not prices.normalPrice then
if not TSM.db.global.disableInvalidMsg then
TSM:Printf(L["Did not cancel %s because your normal price (%s) is invalid. Check your settings."], itemLink or itemString, operation.normalPrice)
end
TSM.Log:AddLogRecord(itemString, "cancel", "Skip", "invalid")
elseif operation.cancelRepost and not prices.cancelRepostThreshold then
if not TSM.db.global.disableInvalidMsg then
TSM:Printf(L["Did not cancel %s because your cancel to repost threshold (%s) is invalid. Check your settings."], itemLink or itemString, operation.cancelRepostThreshold)
end
TSM.Log:AddLogRecord(itemString, "cancel", "Skip", "invalid")
elseif not prices.undercut then
if not TSM.db.global.disableInvalidMsg then
TSM:Printf(L["Did not cancel %s because your undercut (%s) is invalid. Check your settings."], itemLink or itemString, operation.undercut)
end
TSM.Log:AddLogRecord(itemString, "cancel", "Skip", "invalid")
elseif prices.maxPrice < prices.minPrice then
if not TSM.db.global.disableInvalidMsg then
TSM:Printf(L["Did not cancel %s because your maximum price (%s) is lower than your minimum price (%s). Check your settings."], itemLink or itemString, operation.maxPrice, operation.minPrice)
end
TSM.Log:AddLogRecord(itemString, "cancel", "Skip", "invalid")
elseif prices.normalPrice < prices.minPrice then
if not TSM.db.global.disableInvalidMsg then
TSM:Printf(L["Did not cancel %s because your normal price (%s) is lower than your minimum price (%s). Check your settings."], itemLink or itemString, operation.normalPrice, operation.minPrice)
end
TSM.Log:AddLogRecord(itemString, "cancel", "Skip", "invalid")
else
return true
end
end
function Cancel:GetScanListAndSetup(GUIRef, options)
-- setup stuff
GUI = GUIRef
options = options or {}
isScanning = true
options.noScan = options.specialMode
isCancelAll = options.specialMode
wipe(cancelQueue)
wipe(currentItem)
wipe(itemsCancelled)
wipe(itemsMissed)
wipe(TSM.operationLookup)
totalToCancel, totalCanceled, count = 0, 0, 0
local tempList, scanList, groupTemp = {}, {}, {}
specialOptions = specialOptions or {}
wipe(specialOptions)
if type(options.specialMode) == "string" then
if strsub(options.specialMode, 1, 1) == "<" then
specialOptions.specialPriceMax = TSMAPI:UnformatTextMoney(strsub(options.specialMode, 2))
isCancelAll = "price"
elseif strsub(options.specialMode, 1, 1) == ">" then
specialOptions.specialPriceMin = TSMAPI:UnformatTextMoney(strsub(options.specialMode, 2))
isCancelAll = "price"
end
end
for i=GetNumAuctionItems("owner"), 1, -1 do
-- ignore sold auctions
if select(13, GetAuctionItemInfo("owner", i)) == 0 then
local itemString = TSMAPI:GetBaseItemString(GetAuctionItemLink("owner", i), true)
if not TSM.db.global.cancelWithBid and select(10, GetAuctionItemInfo("owner", i)) > 0 then
-- we aren't canceling auctions with bids
TSM.Log:AddLogRecord(itemString, "cancel", "Skip", "bid")
else
if specialOptions.specialPriceMin then
-- cancel auctions above some min price
local buyout = select(9, GetAuctionItemInfo("owner", i))
if buyout > 0 and buyout > specialOptions.specialPriceMin then
tempList[itemString] = true
end
elseif specialOptions.specialPriceMax then
-- cancel auctions below some max price
local buyout = select(9, GetAuctionItemInfo("owner", i))
if buyout > 0 and buyout < specialOptions.specialPriceMax then
tempList[itemString] = true
end
elseif options.specialMode == "CancelAll" then
-- cancel all auctions
tempList[itemString] = true
elseif type(options.specialMode) == "number" then
-- cancel low duration auctions
local timeLeft = GetAuctionItemTimeLeft("owner", i)
if timeLeft <= options.specialMode then
tempList[itemString] = true
end
elseif options.specialMode then
-- cancel all items matching filter
local itemName = GetAuctionItemInfo("owner", i)
if strfind(strlower(itemName), strlower(options.specialMode)) then
tempList[itemString] = true
end
elseif options.itemOperations[itemString] then
-- normal cancel scan
local operations = {}
for _, operation in pairs(options.itemOperations[itemString]) do
if operation.cancelUndercut or operation.cancelRepost then
tinsert(operations, operation)
end
end
tempList[itemString] = operations
end
end
end
end
if options.specialMode then
for itemString in pairs(tempList) do
tinsert(scanList, itemString)
end
else
for itemString, operations in pairs(tempList) do
TSM.operationLookup[itemString] = operations
local isValid
for _, operation in pairs(operations) do
if operation.cancelUndercut or operation.cancelRepost then
isValid = true
if not Cancel:ValidateOperation(itemString, operation) then
isValid = nil
break
end
end
end
if isValid then
tinsert(scanList, itemString)
end
end
TSMAPI:FireEvent("AUCTIONING:CANCEL:START", {num=#scanList})
end
return scanList
end
function Cancel:ProcessItem(itemString, noLog)
if isCancelAll then
return Cancel:SpecialScanProcessItem(itemString, noLog)
end
local operations = TSM.operationLookup[itemString]
if not operations then return end
for _, operation in pairs(operations) do
local toCancel, reasonToCancel, reasonNotToCancel, buyout
local cancelAuctions = {}
for i=GetNumAuctionItems("owner"), 1, -1 do
if select(13, GetAuctionItemInfo("owner", i)) == 0 and itemString == TSMAPI:GetBaseItemString(GetAuctionItemLink("owner", i), true) then
local shouldCancel, reason = Cancel:ShouldCancel(i, operation)
if shouldCancel then
shouldCancel.reason = reason
tinsert(cancelAuctions, shouldCancel)
buyout = select(9, GetAuctionItemInfo("owner", i))
else
reasonNotToCancel = reasonNotToCancel or reason
buyout = buyout or select(9, GetAuctionItemInfo("owner", i))
end
end
end
local numKept = 0
sort(cancelAuctions, function(a, b) return a.buyout < b.buyout end) --keepPosted
for i=#cancelAuctions, 1, -1 do
local auction = cancelAuctions[i]
if (auction.reason == "whitelistUndercut" or auction.reason == "undercut" or auction.reason == "notLowest") and numKept < operation.keepPosted then
numKept = numKept + 1
reasonNotToCancel = "keepPosted"
else
toCancel = true
reasonToCancel = auction.reason
totalToCancel = totalToCancel + 1
tinsert(cancelQueue, auction)
end
end
if totalToCancel > 0 then
TSM.Manage:UpdateStatus("manage", totalCanceled, totalToCancel)
end
if not noLog then
if toCancel then
TSM.Log:AddLogRecord(itemString, "cancel", "Cancel", reasonToCancel, operation, buyout)
elseif reasonNotToCancel then
TSM.Log:AddLogRecord(itemString, "cancel", "Skip", reasonNotToCancel, operation, buyout)
end
end
if #cancelQueue > 0 and not currentItem.buyout then
Cancel:SetupForAction()
end
end
end
function Cancel:SpecialScanProcessItem(itemString, noLog)
local toCancel, reasonToCancel, reasonNotToCancel
local cancelAuctions = {}
for i=GetNumAuctionItems("owner"), 1, -1 do
if select(13, GetAuctionItemInfo("owner", i)) == 0 and itemString == TSMAPI:GetBaseItemString(GetAuctionItemLink("owner", i), true) then
local _, _, quantity, _, _, _, bid, _, buyout, activeBid, _, _, wasSold = GetAuctionItemInfo("owner", i)
local cancelData = {itemString=itemString, stackSize=quantity, buyout=buyout, bid=bid, index=i, numStacks=1}
if specialOptions.specialPriceMin then
if buyout > specialOptions.specialPriceMin then
cancelData.reason = "cancelAll"
tinsert(cancelAuctions, cancelData)
else
reasonNotToCancel = reasonNotToCancel or "cancelAll"
end
elseif specialOptions.specialPriceMax then
if buyout < specialOptions.specialPriceMax then
cancelData.reason = "cancelAll"
tinsert(cancelAuctions, cancelData)
else
reasonNotToCancel = reasonNotToCancel or "cancelAll"
end
elseif type(isCancelAll) ~= "number" or GetAuctionItemTimeLeft("owner", i) <= isCancelAll then
cancelData.reason = "cancelAll"
tinsert(cancelAuctions, cancelData)
else
reasonNotToCancel = reasonNotToCancel or "cancelAll"
end
end
end
local numKept = 0
sort(cancelAuctions, function(a, b) return a.buyout < b.buyout end) --keepPosted
for i=#cancelAuctions, 1, -1 do
local auction = cancelAuctions[i]
toCancel = true
reasonToCancel = auction.reason
totalToCancel = totalToCancel + 1
tinsert(cancelQueue, auction)
end
if totalToCancel > 0 then
TSM.Manage:UpdateStatus("manage", totalCanceled, totalToCancel)
end
if not noLog then
if toCancel then
TSM.Log:AddLogRecord(itemString, "cancel", "Cancel", reasonToCancel)
elseif reasonNotToCancel then
TSM.Log:AddLogRecord(itemString, "cancel", "Skip", reasonNotToCancel)
end
end
if #cancelQueue > 0 and not currentItem.buyout then
Cancel:SetupForAction()
end
end
function Cancel:ShouldCancel(index, operation)
local _, _, quantity, _, _, _, bid, _, buyout, activeBid, _, _, wasSold = GetAuctionItemInfo("owner", index)
local buyoutPerItem = floor(buyout / quantity)
local bidPerItem = floor(bid / quantity)
if operation.matchStackSize and quantity ~= operation.stackSize then return end
local itemString = TSMAPI:GetBaseItemString(GetAuctionItemLink("owner", index), true)
local cancelData = {itemString=itemString, stackSize=quantity, buyout=buyout, bid=bid, index=index, numStacks=1, operation=operation}
local auctionItem = TSM.Scan.auctionData[itemString]
local lowestBuyout, lowestBid, lowestOwner, isWhitelist, isPlayer, isInvalidSeller = TSM.Scan:GetLowestAuction(itemString, operation)
local secondLowest = TSM.Scan:GetSecondLowest(itemString, lowestBuyout, operation) or 0
if wasSold == 1 or not lowestOwner then
-- if this auction was sold or we don't have any data on it then this request is invalid
return
elseif isInvalidSeller or not lowestBuyout then
if isInvalidSeller then
TSM:Printf(L["Seller name of lowest auction for item %s was not returned from server. Skipping this item."], GetAuctionItemLink("owner", index))
else
TSM:Printf(L["Invalid scan data for item %s. Skipping this item."], GetAuctionItemLink("owner", index))
end
return false, "invalidSeller"
end
if not TSM.db.global.cancelWithBid and activeBid > 0 then
-- Don't cancel an auction if it has a bid and we're set to not cancel those
return false, "bid"
end
local prices = TSM.Util:GetItemPrices(operation, itemString)
if buyoutPerItem < prices.minPrice then
-- this auction is below min price
if operation.cancelRepost and prices.resetPrice and buyoutPerItem < (prices.resetPrice - prices.cancelRepostThreshold) then
-- canceling to post at reset price
return cancelData, "reset"
end
return false, "belowMinPrice"
elseif lowestBuyout < prices.minPrice then
-- lowest buyout is below min price, so do nothing
return false, "belowMinPrice"
else
-- lowest buyout is above the min price
if operation.cancelUndercut and (buyoutPerItem - prices.undercut) > (TSM.Scan:GetPlayerLowestBuyout(auctionItem, operation) or math.huge) then
-- this is not our lowest auction
return cancelData, "notLowest"
elseif auctionItem:IsPlayerOnly() then
-- we are posted at the aboveMax price with no competition under our max price
-- check if we can repost higher
if operation.cancelRepost and prices.normalPrice - buyoutPerItem > prices.cancelRepostThreshold then
-- we can repost higher
return cancelData, "repost"
end
return false, "atNormal"
elseif isPlayer and (secondLowest > prices.maxPrice) then
-- we are posted at the aboveMax price with no competition under our max price
-- check if we can repost higher
if operation.cancelRepost and prices.aboveMax - buyoutPerItem > prices.cancelRepostThreshold then
-- we can repost higher
return cancelData, "repost"
end
return false, "atAboveMax"
elseif isPlayer then
-- we are the loewst auction
-- check if we can repost higher
if operation.cancelRepost and ((secondLowest - prices.undercut) - lowestBuyout) > prices.cancelRepostThreshold then
-- we can repost higher
return cancelData, "repost"
end
return false, "notUndercut"
elseif not operation.cancelUndercut then
return -- we're undercut but not canceling undercut auctions
elseif isWhitelist and buyoutPerItem == lowestBuyout then
-- at whitelisted player price
return false, "atWhitelist"
elseif not isWhitelist then
-- we've been undercut by somebody not on our whitelist
return cancelData, "undercut"
elseif buyoutPerItem ~= lowestBuyout or bidPerItem ~= lowestBid then
-- somebody on our whitelist undercut us (or their bid is lower)
return cancelData, "whitelistUndercut"
end
end
error("unexpectedly reached end", buyoutPerItem, lowestBuyout, isWhitelist, isPlayer, prices.minPrice)
end
-- register events and queue up the first item to cancel
function Cancel:SetupForAction()
Cancel:RegisterEvent("CHAT_MSG_SYSTEM")
Cancel:RegisterEvent("UI_ERROR_MESSAGE")
TSM.Manage:UpdateStatus("manage", totalCanceled, totalToCancel)
wipe(currentItem)
currentItem = cancelQueue[1]
TSM.Manage:SetCurrentItem(currentItem)
GUI.buttons:Enable()
end
-- Check if an auction was canceled and move on if so
function Cancel:CHAT_MSG_SYSTEM(_, msg)
if msg == ERR_AUCTION_REMOVED then
count = count + 1
TSM.Manage:UpdateStatus("confirm", count, totalToCancel)
end
end
-- "Item Not Found" error
function Cancel:UI_ERROR_MESSAGE(event, msg)
if msg == ERR_ITEM_NOT_FOUND then
count = count + 1
TSM.Manage:UpdateStatus("confirm", count, totalToCancel)
tinsert(itemsMissed, itemsCancelled[count])
end
end
local function CountFrame()
if count == totalToCancel then
TSMAPI:CancelFrame("cancelCountFrame")
Cancel:Stop()
end
end
local function DelayFrame()
if not isScanning and #(cancelQueue) == 0 then
TSMAPI:CreateFunctionRepeat("cancelCountFrame", CountFrame)
TSMAPI:CancelFrame("cancelDelayFrame")
elseif #(cancelQueue) > 0 then
Cancel:UpdateItem()
TSMAPI:CancelFrame("cancelDelayFrame")
end
end
-- updates the current item to the first one in the list
function Cancel:UpdateItem(...)
local count = ...
4 years ago
if #(cancelQueue) == 0 then
GUI.buttons:Disable()
if isScanning then
TSMAPI:CreateFunctionRepeat("cancelDelayFrame", DelayFrame)
else
TSMAPI:CreateFunctionRepeat("cancelCountFrame", CountFrame)
end
return
end
sort(cancelQueue, function(a, b) return (a.index or 0)>(b.index or 0) end)
if count then
totalCanceled = totalCanceled + count
else
totalCanceled = totalCanceled + 1
end
4 years ago
TSM.Manage:UpdateStatus("manage", totalCanceled, totalToCancel)
wipe(currentItem)
currentItem = cancelQueue[1]
TSM.Manage:SetCurrentItem(currentItem)
GUI.buttons:Enable()
end
-- cancel auctions up to 100 at a time
function Cancel:BulkCancel()
local count = 0
-- list may be unsorted before we start
sort(cancelQueue, function(a, b) return (a.index or 0)>(b.index or 0) end)
print(#(cancelQueue))
for j=1,#(cancelQueue) do
CancelAuction(cancelQueue[1].index)
tinsert(itemsCancelled, CopyTable(cancelQueue[1]))
tremove(cancelQueue, 1)
count = count + 1
if j >= 100 then break end -- 100 items, stop processing cancels
end
print(#(cancelQueue), count)
Cancel:UpdateItem(count)
end
4 years ago
-- cancel the current item (gets called when the button is pressed
function Cancel:DoAction()
local index, backupIndex
-- make sure the currentItem is accurate
if cancelQueue[1].itemString ~= currentItem.itemString then
Cancel:UpdateItem()
end
-- figure out which index the item goes to
for i=GetNumAuctionItems("owner"), 1, -1 do
local _, _, quantity, _, _, _, bid, _, buyout, activeBid = GetAuctionItemInfo("owner", i)
local itemString = TSMAPI:GetBaseItemString(GetAuctionItemLink("owner", i), true)
if itemString == currentItem.itemString and abs((buyout or 0) - (currentItem.buyout or 0)) < quantity and abs((bid or 0) - (currentItem.bid or 0)) < quantity and (not TSM.db.global.cancelWithBid and activeBid == 0 or TSM.db.global.cancelWithBid) then
if not tempIndexList[itemString..buyout..bid..i] then
tempIndexList[itemString..buyout..bid..i] = true
index = i
break
else
backupIndex = i
end
end
end
-- if we found an index then cancel the item
if index then
CancelAuction(index)
elseif backupIndex then
CancelAuction(backupIndex)
end
-- disable the button and move onto the next item
GUI.buttons:Disable()
tinsert(itemsCancelled, CopyTable(cancelQueue[1]))
tremove(cancelQueue, 1)
Cancel:UpdateItem()
end
-- gets called when the "Skip Item" button is pressed
function Cancel:SkipItem()
tremove(cancelQueue, 1)
count = count + 1
TSM.Manage:UpdateStatus("confirm", count, totalToCancel)
Cancel:UpdateItem()
end
-- we are done canceling (maybe)
function Cancel:Stop(interrupted)
wipe(tempIndexList)
if #itemsMissed == 0 or interrupted then
-- didn't get "item not found" for any cancels or we were interrupted so we are done
TSMAPI:CancelFrame("cancelCountFrame")
TSMAPI:CancelFrame("cancelDelayFrame")
TSMAPI:CancelFrame("updateCancelStatus")
GUI:Stopped()
Cancel:UnregisterAllEvents()
wipe(currentItem)
totalToCancel, totalCanceled = 0, 0
isScanning = false
else -- got an "item not found" so requeue ones that we missed
count = totalToCancel
for _, info in ipairs(itemsMissed) do
Cancel:ProcessItem(info.itemString, true)
end
wipe(itemsMissed)
isScanning = false
Cancel:UpdateItem()
end
end
function Cancel:DoneScanning()
isScanning = false
return totalToCancel
end
function Cancel:GetCurrentItem()
return currentItem
end