-- ------------------------------------------------------------------------------ --
-- 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 = ...
if count then
totalCanceled = totalCanceled + count
else
totalCanceled = totalCanceled + 1
end
TSM.Manage : UpdateStatus ( " manage " , totalCanceled , totalToCancel )
if # ( cancelQueue ) == 0 or totalCanceled >= totalToCancel 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 )
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 )
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
Cancel : UpdateItem ( count )
end
-- 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