399 lines
13 KiB
Lua
399 lines
13 KiB
Lua
-- ------------------------------------------------------------------------------ --
|
|
-- TradeSkillMaster --
|
|
-- https://tradeskillmaster.com --
|
|
-- All Rights Reserved - Detailed license information included with addon. --
|
|
-- ------------------------------------------------------------------------------ --
|
|
|
|
local _, TSM = ...
|
|
local MailTracking = TSM.Init("Service.MailTracking")
|
|
local Database = TSM.Include("Util.Database")
|
|
local Delay = TSM.Include("Util.Delay")
|
|
local Event = TSM.Include("Util.Event")
|
|
local TempTable = TSM.Include("Util.TempTable")
|
|
local Log = TSM.Include("Util.Log")
|
|
local ItemString = TSM.Include("Util.ItemString")
|
|
local ItemInfo = TSM.Include("Service.ItemInfo")
|
|
local Settings = TSM.Include("Service.Settings")
|
|
local AuctionTracking = TSM.Include("Service.AuctionTracking")
|
|
local private = {
|
|
settings = nil,
|
|
mailDB = nil,
|
|
itemDB = nil,
|
|
quantityDB = nil,
|
|
isOpen = false,
|
|
tooltip = nil,
|
|
callbacks = {},
|
|
expiresCallbacks = {},
|
|
cancelAuctionQuery = nil,
|
|
}
|
|
local PLAYER_NAME = UnitName("player")
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Module Loading
|
|
-- ============================================================================
|
|
|
|
MailTracking:OnSettingsLoad(function()
|
|
private.settings = Settings.NewView()
|
|
:AddKey("factionrealm", "internalData", "pendingMail")
|
|
:AddKey("factionrealm", "internalData", "expiringMail")
|
|
:AddKey("sync", "internalData", "mailQuantity")
|
|
|
|
-- update the structure of TSM.db.factionrealm.internalData.pendingMail
|
|
local toUpdate = TempTable.Acquire()
|
|
for character, pendingMailData in pairs(private.settings.pendingMail) do
|
|
if pendingMailData.items then
|
|
Log.Info("Converting pending mail data for %s", character)
|
|
toUpdate[character] = pendingMailData.items
|
|
end
|
|
end
|
|
for character, items in pairs(toUpdate) do
|
|
private.settings.pendingMail[character] = items
|
|
end
|
|
TempTable.Release(toUpdate)
|
|
|
|
-- remove data for characters we don't own
|
|
local toRemove = TempTable.Acquire()
|
|
for character in pairs(private.settings.pendingMail) do
|
|
if Settings.GetCharacterSyncAccountKey(character) ~= Settings.GetCurrentSyncAccountKey() then
|
|
Log.Info("Removed pending mail data for %s", character)
|
|
tinsert(toRemove, character)
|
|
end
|
|
end
|
|
for _, character in ipairs(toRemove) do
|
|
private.settings.pendingMail[character] = nil
|
|
end
|
|
TempTable.Release(toRemove)
|
|
|
|
private.mailDB = Database.NewSchema("MAIL_TRACKING_INBOX_INFO")
|
|
:AddUniqueNumberField("index")
|
|
:AddStringField("icon")
|
|
:AddStringField("sender")
|
|
:AddStringField("subject")
|
|
:AddStringField("itemString")
|
|
:AddNumberField("itemCount")
|
|
:AddNumberField("money")
|
|
:AddNumberField("cod")
|
|
:AddNumberField("expires")
|
|
:AddIndex("index")
|
|
:Commit()
|
|
private.itemDB = Database.NewSchema("MAIL_TRACKING_INBOX_ITEMS")
|
|
:AddNumberField("index")
|
|
:AddNumberField("itemIndex")
|
|
:AddStringField("itemLink")
|
|
:AddNumberField("quantity")
|
|
:Commit()
|
|
private.quantityDB = Database.NewSchema("MAIL_TRACKING_QUANTITY")
|
|
:AddUniqueStringField("itemString")
|
|
:AddNumberField("quantity")
|
|
:Commit()
|
|
|
|
private.settings.pendingMail[PLAYER_NAME] = private.settings.pendingMail[PLAYER_NAME] or {}
|
|
Event.Register("MAIL_SHOW", private.MailShowHandler)
|
|
Event.Register("MAIL_CLOSED", private.MailClosedHandler)
|
|
Event.Register("MAIL_INBOX_UPDATE", private.MailInboxUpdateHandler)
|
|
|
|
if TSM.IsWowClassic() then
|
|
-- handle auction buying
|
|
hooksecurefunc("PlaceAuctionBid", function(listType, index, bidPlaced)
|
|
local itemString = ItemString.GetBase(GetAuctionItemLink(listType, index))
|
|
local _, _, stackSize, _, _, _, _, _, _, buyout = GetAuctionItemInfo(listType, index)
|
|
if not itemString or bidPlaced ~= buyout then
|
|
return
|
|
end
|
|
private.ChangePendingMailQuantity(itemString, stackSize)
|
|
end)
|
|
|
|
-- handle auction canceling
|
|
hooksecurefunc("CancelAuction", function(index)
|
|
local itemString = ItemString.GetBase(GetAuctionItemLink("owner", index))
|
|
local _, _, stackSize = GetAuctionItemInfo("owner", index)
|
|
-- for some reason, these APIs don't always work properly, so check the return values
|
|
if not itemString or not stackSize or stackSize == 0 then
|
|
return
|
|
end
|
|
private.ChangePendingMailQuantity(itemString, stackSize)
|
|
end)
|
|
else
|
|
private.cancelAuctionQuery = AuctionTracking.CreateQuery()
|
|
:Equal("auctionId", Database.BoundQueryParam())
|
|
:Select("itemString", "stackSize")
|
|
|
|
-- handle auction canceling
|
|
hooksecurefunc(C_AuctionHouse, "CancelAuction", function(auctionId)
|
|
private.cancelAuctionQuery:BindParams(auctionId)
|
|
for _, itemString, stackSize in private.cancelAuctionQuery:Iterator() do
|
|
private.ChangePendingMailQuantity(itemString, stackSize)
|
|
end
|
|
end)
|
|
end
|
|
|
|
-- handle sending mail to alts
|
|
hooksecurefunc("SendMail", function(target)
|
|
local character = private.ValidateCharacter(target)
|
|
if not character then
|
|
return
|
|
end
|
|
private.settings.pendingMail[character] = private.settings.pendingMail[character] or {}
|
|
local altPendingMail = private.settings.pendingMail[character]
|
|
for i = 1, ATTACHMENTS_MAX_SEND do
|
|
local itemString = ItemString.GetBase(GetSendMailItemLink(i))
|
|
local _, _, _, quantity = GetSendMailItem(i)
|
|
if itemString and quantity then
|
|
altPendingMail[itemString] = (altPendingMail[itemString] or 0) + quantity
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- handle returning mail to alts
|
|
hooksecurefunc("ReturnInboxItem", function(index)
|
|
local character = private.ValidateCharacter(select(3, GetInboxHeaderInfo(index)))
|
|
if not character then
|
|
return
|
|
end
|
|
private.settings.pendingMail[character] = private.settings.pendingMail[character] or {}
|
|
local altPendingMail = private.settings.pendingMail[character]
|
|
for i = 1, ATTACHMENTS_MAX_SEND do
|
|
local _, _, _, quantity = GetInboxItem(index, i)
|
|
local itemLink = quantity and quantity > 0 and private.GetInboxItemLink(index, i) or nil
|
|
local itemString = itemLink and ItemString.GetBase(itemLink) or nil
|
|
if itemString then
|
|
altPendingMail[itemString] = (altPendingMail[itemString] or 0) + quantity
|
|
end
|
|
end
|
|
end)
|
|
|
|
private.quantityDB:BulkInsertStart()
|
|
for itemString, quantity in pairs(private.settings.pendingMail[PLAYER_NAME]) do
|
|
if quantity > 0 then
|
|
private.quantityDB:BulkInsertNewRow(itemString, quantity)
|
|
else
|
|
private.settings.pendingMail[PLAYER_NAME][itemString] = nil
|
|
end
|
|
end
|
|
private.quantityDB:BulkInsertEnd()
|
|
end)
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Module Functions
|
|
-- ============================================================================
|
|
|
|
function MailTracking.RegisterCallback(callback)
|
|
tinsert(private.callbacks, callback)
|
|
end
|
|
|
|
function MailTracking.RegisterExpiresCallback(callback)
|
|
tinsert(private.expiresCallbacks, callback)
|
|
end
|
|
|
|
function MailTracking.BaseItemIterator()
|
|
return private.quantityDB:NewQuery()
|
|
:Select("itemString")
|
|
:IteratorAndRelease()
|
|
end
|
|
|
|
function MailTracking.CreateMailInboxQuery()
|
|
return private.mailDB:NewQuery()
|
|
end
|
|
|
|
function MailTracking.CreateMailItemQuery()
|
|
return private.itemDB:NewQuery()
|
|
end
|
|
|
|
function MailTracking.GetInboxItemLink(index)
|
|
return private.GetInboxItemLink(index, 1)
|
|
end
|
|
|
|
function MailTracking.GetMailType(index)
|
|
return private.GetMailType(index)
|
|
end
|
|
|
|
function MailTracking.GetQuantityByBaseItemString(baseItemString)
|
|
return private.quantityDB:GetUniqueRowField("itemString", baseItemString, "quantity") or 0
|
|
end
|
|
|
|
function MailTracking.RecordAuctionBuyout(baseItemString, stackSize)
|
|
if TSM.IsWowClassic() then
|
|
-- on classic, we'll handle auction buys via a direct hook
|
|
return
|
|
end
|
|
private.ChangePendingMailQuantity(baseItemString, stackSize)
|
|
end
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Event Handlers
|
|
-- ============================================================================
|
|
|
|
function private.MailShowHandler()
|
|
private.isOpen = true
|
|
end
|
|
|
|
function private.MailClosedHandler()
|
|
private.isOpen = false
|
|
end
|
|
|
|
function private.MailInboxUpdateHandler()
|
|
if not private.isOpen then
|
|
return
|
|
end
|
|
|
|
Delay.AfterFrame("mailInboxScan", 1, private.MailInboxUpdateDelayed)
|
|
end
|
|
|
|
function private.MailInboxUpdateDelayed()
|
|
if not private.isOpen then
|
|
return
|
|
end
|
|
|
|
wipe(private.settings.mailQuantity)
|
|
wipe(private.settings.pendingMail[PLAYER_NAME])
|
|
private.mailDB:TruncateAndBulkInsertStart()
|
|
private.itemDB:TruncateAndBulkInsertStart()
|
|
local expiration = math.huge
|
|
for i = 1, GetInboxNumItems() do
|
|
local _, _, sender, subject, money, cod, daysLeft, itemCount = GetInboxHeaderInfo(i)
|
|
if itemCount and itemCount > 0 and money and money > 0 then
|
|
expiration = min(expiration, time() + (daysLeft * 24 * 60 * 60))
|
|
end
|
|
|
|
if money and money > 0 then
|
|
private.itemDB:BulkInsertNewRow(i, 0, tostring(money), 0)
|
|
end
|
|
|
|
local firstItemString = nil
|
|
for j = 1, ATTACHMENTS_MAX do
|
|
local _, _, _, quantity = GetInboxItem(i, j)
|
|
local itemLink = quantity and quantity > 0 and private.GetInboxItemLink(i, j) or nil
|
|
local itemString = itemLink and ItemString.Get(itemLink) or nil
|
|
if itemString then
|
|
firstItemString = firstItemString or itemString
|
|
local baseItemString = ItemString.GetBaseFast(itemString)
|
|
private.settings.mailQuantity[baseItemString] = (private.settings.mailQuantity[baseItemString] or 0) + quantity
|
|
private.itemDB:BulkInsertNewRow(i, j, itemLink, quantity)
|
|
end
|
|
end
|
|
|
|
local mailType = private.GetMailType(i, firstItemString) or ""
|
|
if mailType == "BUY" then
|
|
local _, _, _, bid = GetInboxInvoiceInfo(i)
|
|
cod = bid
|
|
end
|
|
|
|
private.mailDB:BulkInsertNewRow(i, mailType, sender or UNKNOWN, subject or "--", firstItemString or "", itemCount or 0, money or 0, cod or 0, daysLeft)
|
|
end
|
|
private.quantityDB:TruncateAndBulkInsertStart()
|
|
for itemString, quantity in pairs(private.settings.mailQuantity) do
|
|
private.quantityDB:BulkInsertNewRow(itemString, quantity)
|
|
end
|
|
private.quantityDB:BulkInsertEnd()
|
|
private.itemDB:BulkInsertEnd()
|
|
private.mailDB:BulkInsertEnd()
|
|
|
|
private.settings.expiringMail[PLAYER_NAME] = expiration ~= math.huge and expiration or nil
|
|
for _, callback in ipairs(private.expiresCallbacks) do
|
|
callback()
|
|
end
|
|
for _, callback in ipairs(private.callbacks) do
|
|
callback()
|
|
end
|
|
end
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Private Helper Functions
|
|
-- ============================================================================
|
|
|
|
function private.ChangePendingMailQuantity(itemString, quantity)
|
|
assert(quantity ~= 0)
|
|
private.settings.pendingMail[PLAYER_NAME][itemString] = (private.settings.pendingMail[PLAYER_NAME][itemString] or 0) + quantity
|
|
if not private.quantityDB:HasUniqueRow("itemString", itemString) then
|
|
-- create a new row
|
|
private.quantityDB:NewRow()
|
|
:SetField("itemString", itemString)
|
|
:SetField("quantity", quantity)
|
|
:Create()
|
|
else
|
|
local row = private.quantityDB:GetUniqueRow("itemString", itemString)
|
|
local newValue = row:GetField("quantity") + quantity
|
|
assert(newValue >= 0)
|
|
if newValue == 0 then
|
|
-- remove this row
|
|
private.quantityDB:DeleteRow(row)
|
|
else
|
|
-- update this row
|
|
row:SetField("quantity", newValue)
|
|
:Update()
|
|
end
|
|
row:Release()
|
|
end
|
|
for _, callback in ipairs(private.callbacks) do
|
|
callback()
|
|
end
|
|
end
|
|
|
|
function private.ValidateCharacter(character)
|
|
if not character then
|
|
return
|
|
end
|
|
local characterName, realm = strsplit("-", strlower(character))
|
|
-- we only care to track mails with characters on this realm
|
|
if realm and realm ~= strlower(GetRealmName()) then
|
|
return
|
|
end
|
|
-- we only care to track mails with characters on this account
|
|
local result = nil
|
|
for _, name in Settings.CharacterByAccountFactionrealmIterator() do
|
|
if strlower(name) == characterName then
|
|
result = name
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
|
|
function private.GetInboxItemLink(index, num)
|
|
local link = GetInboxItemLink(index, num)
|
|
if ItemString.GetBase(link) ~= ItemString.GetPetCage() then
|
|
return link
|
|
end
|
|
|
|
-- need to do tooltip scanning to get battlepet links
|
|
private.tooltip = private.tooltip or CreateFrame("GameTooltip", "TSM4MailingInboxTooltip", UIParent, "GameTooltipTemplate")
|
|
private.tooltip:SetOwner(UIParent, "ANCHOR_NONE")
|
|
private.tooltip:ClearLines()
|
|
|
|
local _, speciesId, level, breedQuality = private.tooltip:SetInboxItem(index, num)
|
|
assert(speciesId and speciesId > 0)
|
|
private.tooltip:Hide()
|
|
return ItemInfo.GetLink(strjoin(":", "p", speciesId, level, breedQuality))
|
|
end
|
|
|
|
function private.GetMailType(index, firstItemString)
|
|
local _, _, _, subject, money, cod, _, numItems, _, _, _, _, isGM = GetInboxHeaderInfo(index)
|
|
if isGM or (cod and cod > 0) or (money == 0 and (not numItems or numItems == 0)) then
|
|
return nil
|
|
end
|
|
|
|
local info = GetInboxInvoiceInfo(index)
|
|
if money and money > 0 and info == "seller" then
|
|
return "SALE"
|
|
elseif numItems and numItems > 0 and info == "buyer" then
|
|
return "BUY"
|
|
elseif not info then
|
|
if strfind(subject, string.gsub("^"..AUCTION_REMOVED_MAIL_SUBJECT, "%%s", "")) then
|
|
return "CANCEL"
|
|
end
|
|
|
|
if strfind(subject, string.gsub("^"..AUCTION_EXPIRED_MAIL_SUBJECT, "%%s", "")) then
|
|
return "EXPIRE"
|
|
end
|
|
end
|
|
|
|
return "OTHER"
|
|
end
|