AuctioneerSuite/BeanCounter/BeanCounterAPI.lua
2026-04-13 17:48:13 -04:00

553 lines
22 KiB
Lua

--[[
Auctioneer Addon for World of Warcraft(tm).
Version: 5.9.4961 (WhackyWallaby)
Revision: $Id: BeanCounterAPI.lua 4933 2010-10-13 17:16:14Z Nechckn $
BeanCounterAPI - Functions for other addons to get BeanCounter Data
URL: http://auctioneeraddon.com/
License:
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program(see GPL.txt); if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Note:
This AddOn's source code is specifically designed to work with
World of Warcraft's interpreted AddOn system.
You have an implicit license to use this AddOn with these facilities
since that is it's designated purpose as per:
http://www.fsf.org/licensing/licenses/gpl-faq.html#InterpreterIncompat
]]
LibStub("LibRevision"):Set("$URL: http://svn.norganna.org/auctioneer/branches/5.9/BeanCounter/BeanCounterAPI.lua $","$Rev: 4933 $","5.1.DEV.", 'auctioneer', 'libs')
local lib = BeanCounter
lib.API = {}
local private, print, get, set, _BC = lib.getLocals()
--[[Use lib.API.isLoaded to check if beancounter is ready for use. It is false until DB is ready and all gui and API elements have been created]]
local type,select,strsplit,strjoin,ipairs,pairs = type,select,strsplit,strjoin,ipairs,pairs
local tostring,tonumber,strlower = tostring,tonumber,strlower
local tinsert,tremove,sort = tinsert,tremove,sort
local wipe = wipe
local time = time
local GetRealmName = GetRealmName
-- GLOBALS: BeanCounter, BeanCounterDB
local function debugPrint(...)
if get("util.beancounter.debugAPI") then
private.debugPrint("BeanCounterAPI",...)
end
end
--[[External Search Stub, allows other addons searches to search to display in BC or get results of a BC search
Can be item Name or Link or itemID
If itemID or Link search will be faster than a plain text lookup
]]
local _
function lib.API.search(name, settings, queryReturn)
if get("util.beancounter.externalSearch") then --is option enabled and have we already searched for this name (stop spam)
--check for blank search request
if name == "" and not queryReturn then return end
name = tostring(name)
--serverName is used as part of our cache ID string
local serverName
if settings and settings.servers and settings.servers[1] then
serverName = settings.servers[1]
else
serverName = GetRealmName()
end
--playerName is also used as part of our cache ID string
local playerName
if settings and settings.selectbox and settings.selectbox[2] then
playerName = settings.selectbox[2]
else
playerName = "server"
end
--the function getItemInfo will return a plain text name on itemID or itemLink searches and nil if a plain text search is passed
local itemID, itemLink, itemName
itemID, itemLink = private.getItemInfo(name, "itemid")
if not itemLink then
itemName = name
else
_, itemName = lib.API.getItemString(itemLink)
end
local cached = private.checkSearchCache(itemName, serverName, playerName)
--return cached search
if queryReturn and cached then
return cached
end
--if API query lacks a settings table use whatever filter options the player has currently selected
if not settings then
settings = private.getCheckboxSettings()
end
--search data
if itemLink then
--itemKey is used to filter results if exact is used. We need the key to remove of the XXX style items from returns
_, settings.suffix = lib.API.decodeLink(itemLink)
if settings.suffix == 0 then settings.suffix = nil end
--cache search request
private.searchByItemID(itemID, settings, queryReturn, nil, nil, itemName)
else
private.startSearch(itemName, settings, queryReturn)
end
--return data or displayItemName in select box
if queryReturn then
return private.checkSearchCache(itemName, serverName, playerName)
else
private.frame.searchBox:SetText(itemName)
end
end
end
-- Cache system for searches
local cache = setmetatable({}, {__mode="v"})
function private.checkSearchCache(name, serverName, playerName)
if not name or not serverName or not playerName then return end --nil safe the cache check
return cache[strlower(name)..serverName..playerName]
end
function private.addSearchCache(name, data, serverName, playerName)
if not name or not serverName or not playerName then return end --nil safe the cache add
cache[strlower(name)..serverName..playerName] = data
end
function private.wipeSearchCache()
wipe(cache)
end
--[[ Returns the Sum of all AH sold vs AH buys along with the date range
If no player name is supplied then the entire server profit will be totaled
if no item name is provided then all items will be returned
if no date range is supplied then a sufficently large range to cover the entire BeanCounter History will be used.
The meta data option mean s we will add in the estimated value from an items discenchant.
This really only works when looking ONLY at the bid/buy datasets. When looking at sales as well you will end up with higher than expected profits
]]
local function addDEValue(meta)
local mat, count, value = meta:match("DE:(%d-):(%d-):(%d-)|")
if mat and count and value then
return value*count
end
return 0
end
function lib.API.getAHProfit(player, item, lowDate, highDate, includeMeta)
if not player or player == "" then player = "server" end
if not item then item = "" end
local sum, low, high, date = 0, 9999999999, 0
local settings = {["selectbox"] = {"1", player} , ["bid"] = true, ["auction"] = true, ["failedauction"] = true}
local tbl
--allow a already API searched data table to be passed instead of just a text string
if type(item) == "string" then
tbl = private.startSearch(item, settings, true)
elseif type(item) == "table" then
tbl = item
end
if not tbl then return end
for i,v in pairs(tbl) do
date = tonumber(v[12])
--if user passes a low and high date to use, filter out any not in the range
local dateRange = true
if date and lowDate and highDate then--if we have high/low then set range to false
dateRange = false
if lowDate < date and date < highDate then --set back to true if we meet conditions
dateRange = true
end
end
if dateRange then
--store lower and upper date ranges
if date and date < low then low = date end
if date and date > high then high = date end
--Sum the trxns
if v[2] == _BC('UiAucSuccessful') then
sum = sum + v[5] - v[9] --sum sale - deposit. fee's have already been subtracted
elseif v[2] == _BC('UiAucExpired') then
sum = sum - v[9] --subtract failed deposits
elseif v[2] == _BC('UiWononBid') then
sum = sum - v[3] --subtract bought items
if includeMeta then
sum = sum + addDEValue(v[14]) --meta data is the exact mats/count/and market value at time of de
end
elseif v[2] == _BC('UiWononBuyout') then
sum = sum - v[4]
if includeMeta then
sum = sum + addDEValue(v[14])
end
end
end
end
return sum, lowDate or low, highDate or high
end
--[[This will return profits in date segments allow easy to create graphs
Similar to API.getProfit() This utility return a table containing the profit earned in day based segments. useful for graphing a change over time
example: entering (player, "arcane dust", 7) would return the the profit for arcane dust in 7 day segments starting from most recent to oldest
]]
function lib.API.getAHProfitGraph(player, item ,days)
if not player or player == "" then player = "server" end
if not item then item = "" end
if not days then days = 7 end
--Get data from BeanCounter
local settings = {["selectbox"] = {"1", player} , ["bid"] =true, ["auction"] = true}
local tbl = private.startSearch(item, settings, "none")
--Merge and edit provided table to needed format
for i,v in pairs(tbl) do
for a,b in pairs(v) do
tinsert(tbl, b)
end
end
--remove now redundant table entries
tbl.completedAuctions, tbl["completedBidsBuyouts"], tbl.failedAuctions, tbl.failedBids = nil, nil, nil, nil
--check if we actually have any results from the search
if #tbl == 0 then return {0}, 0, 0 end
--sort by date
sort(tbl, function(a,b) return a[5] > b[5] end)
--get min and max dates.
local high, low, count, sum, number = tbl[1][5], tbl[#tbl][5], 1, 0, 0
local range = high - (days* 86400)
tbl.sums = {}
tbl.sums[count] = {}
for i,v in ipairs(tbl) do
if tonumber(v[5]) >= range then
if v[4] == "Auction successful" then
number = tonumber(v[3]:match(".-;.-;.-;.-;.-;(.-);.*")) or 0
sum = sum + number
elseif v[4] == "Auction won" then
number = tonumber(v[3]:match(".-;.-;.-;.-;.-;.-;(.-);.*")) or 0
sum = sum - number
end
tbl.sums[count] = sum
else
count = count + 1
range = range - (days * 86400)
tbl.sums[count] = {}
sum = 0
if v[4] == "Auction successful" then
number = tonumber(v[3]:match(".-;.-;.-;.-;.-;(.-);.*")) or 0
sum = sum + number
elseif v[4] == "Auction won" then
number = tonumber(v[3]:match(".-;.-;.-;.-;.-;.-;(.-);.*")) or 0
sum = sum - number
end
tbl.sums[count] = sum
end
end
return tbl.sums, low, high
end
--[[
Get Sold / Failed Ratio
Used by match beancounter, made into an API to allow other addons easier access to this data
This returns the Sold/Failed number of auctions and Sold/Failed number of items
Adds ability to use serverKey == realm.."-"..faction
]]
function lib.API.getAHSoldFailed(player, link, days, serverKey)
if not link or not player then return end
--check for server key or use home
local server
if serverKey then
server = lib.API.SplitServerKey(serverKey)
else
server = private.realmName
end
if not BeanCounterDB[server] or not BeanCounterDB[server][player] then return end
local playerData = BeanCounterDB[server][player] --alias
local itemID = lib.API.decodeLink(link)
if not itemID then return end
local now = time()
local success, failed, sucessStack, failedStack = 0, 0, 0, 0
--if we want to filter to a date range then we use this, if we want EVERY trxn uses second lookup
--the second lookup is mesurably faster but not noticable in real use due to not having to expand the DB. 100 trxns may have a 0.0001 sec diffrence
if days then
days = days * 86400 --days to seconds
if playerData["completedAuctions"][itemID] then
for key in pairs(playerData["completedAuctions"][itemID] ) do
for i, text in pairs(playerData["completedAuctions"][itemID][key]) do
local stack, _, _, _, _, _, _, auctime = strsplit(";", text)
auctime, stack = tonumber(auctime), tonumber(stack)
if (now - auctime) < (days) then
success = success + 1
sucessStack = sucessStack + stack
end
end
end
end
if playerData["failedAuctions"][itemID] then
for key in pairs(playerData["failedAuctions"][itemID]) do
for i, text in pairs(playerData["failedAuctions"][itemID][key]) do
local stack, _, _, _, _, _, _, auctime = strsplit(";", text)
auctime, stack = tonumber(auctime), tonumber(stack)
if (now - auctime) < (days) then
failed = failed + 1
failedStack = failedStack + stack
end
end
end
end
else
if private.playerData then
if playerData["completedAuctions"][itemID] then
for key in pairs(playerData["completedAuctions"][itemID] ) do
success = success + #playerData["completedAuctions"][itemID][key]
end
end
if playerData["failedAuctions"][itemID] then
for key in pairs(playerData["failedAuctions"][itemID]) do
failed = failed + #playerData["failedAuctions"][itemID][key]
end
end
end
end
return success, failed, sucessStack, failedStack
end
--[[Change or add a reason code to a transaction]]
function lib.API.updatedReason(serverKey, newReason, itemLink, bid, buy, net, stack, sellerName, deposit, fee, currentReason, Time)
--to string all number values for comparison to stored data
bid, buy, net, stack, deposit, fee, Time = tostring(bid), tostring(buy), tostring(net), tostring(stack), tostring(deposit), tostring(fee), tostring(Time)
--convert ... back to 0
if currentReason == "..." then currentReason = "0" end
if sellerName == "..." then sellerName = "0" end
--if no serverKey provided use current server
local server = lib.API.SplitServerKey(serverKey) or private.realmName
local itemString = lib.API.getItemString(itemLink)
local itemID, suffix = lib.API.decodeLink(itemLink)
for player, playerData in pairs(BeanCounterDB[server]) do
for DB, data in pairs(playerData) do
if DB == "failedBids" or DB == "failedAuctions" or DB == "completedAuctions" or DB == "completedBidsBuyouts" or DB == "failedBidsNeutral" or DB == "failedAuctionsNeutral" or DB == "completedAuctionsNeutral" or DB == "completedBidsBuyoutsNeutral" then
if data[itemID] and data[itemID][itemString] then
for i, text in pairs(data[itemID][itemString]) do
local STACK, NET, DEPOSIT , FEE, BUY , BID, SELLERNAME, TIME, CURRENTREASON, LOCATION = private.unpackString(text)
if currentReason == CURRENTREASON and stack == STACK and sellerName == SELLERNAME and bid == BID and buy == BUY and net == NET and deposit == DEPOSIT and Time == TIME then
local newText = private.packString(STACK, NET, DEPOSIT , FEE, BUY , BID, SELLERNAME, TIME, newReason, LOCATION)
table.remove(data[itemID][itemString], i)
private.databaseAdd(DB, nil, itemString, newText)
private.wipeSearchCache() --clear cached searches
return
end
end
end
end
end
end
end
--**********************************************************************************************************************
--ITEMLINK AND STRING API'S USE THESE IN PLACE OF LOCAL :MATCH() CALLS
--[[ Retrives the itemLink from the name array when passed an itemKey
we store itemKeys with a unique ID but our name array does not
]]
function lib.API.getArrayItemLink(itemString)
local itemID, suffix, uniqueID = lib.API.decodeLink(itemString)
local itemKey = itemID..":"..suffix
if BeanCounterDBNames[itemKey] then
return lib.API.createItemLinkFromArray(itemKey, uniqueID) --uniqueID is used as a scaling factor for "of the" suffix items
end
debugPrint("Searching DB for ItemID..", suffix, itemID, "Failed Item does not exist")
return
end
--[[Converts the compressed link stored in the itemIDArray back to a standard blizzard format]]
function lib.API.createItemLinkFromArray(itemKey, uniqueID)
if BeanCounterDBNames[itemKey] then
if not uniqueID then uniqueID = 0 end
local itemID, suffix = strsplit(":", itemKey)
local color, name = strsplit(";", BeanCounterDBNames[itemKey])
return strjoin("", "|", color, "|Hitem:", itemID,":0:0:0:0:0:", suffix, ":", uniqueID, ":80|h[", name, "]|h|r")
end
return
end
--[[Convert and store an itemLink into teh compressed format used in teh itemIDArray]]
function lib.API.storeItemLinkToArray(itemLink)
if not itemLink then return end
local color, itemID, suffix, name = itemLink:match("|(.-)|Hitem:(.-):.-:.-:.-:.-:.-:(.-):.+|h%[(.-)%]|h|r")
if color and itemID and suffix and name then
BeanCounterDBNames[itemID..":"..suffix] = color..";"..name
end
end
--[[Turns an itemLink into an itemString and extracts the itemName
Returns sanitized itemlinks. Since hyperlinks now vary depending on level of player who looks/creates them
]]
function lib.API.getItemString(itemLink)
if not itemLink or not type(itemLink) == "string" then return end
local itemString, itemName = itemLink:match("H(item:.-)|h%[(.-)%]")
if not itemString then return end
itemString = itemString:gsub("(item:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+:[^:]+):%d+", "%1:80")
return itemString, itemName
end
--[[Returns id, suffix, uniqueID when passed an itemLink or itemString, this a mildly tweaked version of the one found in AucAdv.
Handles Hitem:string, item:string, or full itemlinks
]]
local function breakHyperlink(match, matchlen, ...)
local v
local n = select("#", ...)
for i = 1, n do
v = select(i, ...)
if (v:sub(1,matchlen) == match) then
return strsplit(":", v) --for item:0:... style links bean stores
elseif(v:sub(2, matchlen+1) == match) then
return strsplit(":", v:sub(2)) --for Hitem:0:... normal itemStrings and hyperlinks
end
end
end
function lib.API.decodeLink(link)
local vartype = type(link)
if (vartype == "string") then
local lType, id, enchant, gem1, gem2, gem3, gemBonus, suffix, uniqueID, lichKing = breakHyperlink("item:", 5, strsplit("|", link))
if (lType ~= "item") then return end
return id, suffix, uniqueID
end
return
end
--[[Return REASON codes for tooltip or other use
This allows a way to get it that wont break if I change the internal DB layout
Pass a itemlink and stack count
Returns : "Reason, time of purchase, what you payed" or nil
NOTE: Reason could possibly be "", decided to return data anyways, calling module can decide if it want to use data or not
]]
function lib.API.getBidReason(itemLink, quantity)
if not itemLink or not quantity then return end
local itemString = lib.API.getItemString(itemLink)
local itemID, suffix = lib.API.decodeLink(itemLink)
if private.playerData["completedBidsBuyouts"][itemID] and private.playerData["completedBidsBuyouts"][itemID][itemString] then
for i,v in pairs(private.playerData["completedBidsBuyouts"][itemID][itemString]) do
local quan, _, _ , _, _, bid, _, Time, reason = private.unpackString(v)
if tonumber(quan) == tonumber(quantity) and reason and Time then
return reason, Time, tonumber(bid)
end
end
end
--not found on the current player lets see if we bought it on another player
for player in pairs(private.serverData) do
if private.serverData[player]["completedBidsBuyouts"][itemID] and private.serverData[player]["completedBidsBuyouts"][itemID][itemString] then
for i,v in pairs(private.serverData[player]["completedBidsBuyouts"][itemID][itemString]) do
local quan, _, _ , _, _, bid, _, Time, reason = private.unpackString(v)
if tonumber(quan) == tonumber(quantity) and reason and Time then
return reason, Time, tonumber(bid), player
end
end
end
end
return --if nothing found return nil
end
--[[Any itemlink passed into this function will be prompted to remove from the database]]
function lib.API.deleteItem(itemLink)
if itemLink and itemLink:match("^(|c%x+|Hitem.+|h%[.+%])") then
private.deletePromptFrame.item:SetText(itemLink)
private.deletePromptFrame:Show()
else
print("Invalid itemLink")
end
end
--[[Duplicate of the function in auctioneer, Splits and caches serverKey variable
realm, faction, localizedFaction = lib.API.SplitServerKey(serverKey) ]]
local splitcache = {}
local localizedfactions = {
["Alliance"] = FACTION_ALLIANCE,
["Horde"] = FACTION_HORDE,
["Neutral"] = COMBATLOG_FILTER_STRING_NEUTRAL_UNITS, -- if this is not the right context in other locales, may need to create our own localizer entry
}
function lib.API.SplitServerKey(serverKey)
if not serverKey then return end
local split = splitcache[serverKey]
if not split then
local realm, faction = strmatch(serverKey, "^(.+)%-(%u%l+)$")
local transfaction = localizedfactions[faction]
if not transfaction then return end
split = {realm, faction, realm.." - "..transfaction}
splitcache[serverKey] = split
end
return split[1], split[2], split[3]
end
--[[===========================================================================
--|| Deprecation Alert Functions
--||=========================================================================]]
-- GLOBALS: debugstack, geterrorhandler
--Ths function was created by Shirik all thanks and blame go to him :P
do
local SOURCE_PATTERN = "([^\\/:]+:%d+): in function ([^\"']+)[\"']";
local seenCalls = {};
local uid = 0;
-------------------------------------------------------------------------------
-- Shows a deprecation alert. Indicates that a deprecated function has
-- been called and provides a stack trace that can be used to help
-- find the culprit.
-- @param replacementName (Optional) The displayable name of the replacement function
-- @param comments (Optional) Any extra text to display
-------------------------------------------------------------------------------
function lib.ShowDeprecationAlert(replacementName, comments)
local caller, source, functionName =
debugstack(3):match(SOURCE_PATTERN), -- Keep in mind this will be truncated to only the first in the tuple
debugstack(2):match(SOURCE_PATTERN); -- This will give us both the source and the function name
caller, source, functionName = caller or "Unknown.lua:000", source or "Unknown.lua:000", functionName or "Unknown" --Stop nil errors if data is missing
functionName = functionName .. "()";
-- Check for this source & caller combination
seenCalls[source] = seenCalls[source] or {};
if not seenCalls[source][caller] then
-- Not warned yet, so warn them!
seenCalls[source][caller]=true
-- Display it
debugPrint(
"Auctioneer: "..
functionName .. " has been deprecated and was called by |cFF9999FF"..caller:match("^(.+)%.[lLxX][uUmM][aAlL]:").."|r. "..
(replacementName and ("Please use "..replacementName.." instead. ") or "")..
(comments or "")
);
geterrorhandler()(
"Deprecated function call occurred in BeanCounter API:\n {{{Deprecated Function:}}} "..functionName..
"\n {{{Source Module:}}} "..source:match("^(.+)%.[lLxX][uUmM][aAlL]:")..
"\n {{{Calling Module:}}} "..caller:match("^(.+)%.[lLxX][uUmM][aAlL]:")..
"\n {{{Available Replacement:}}} "..replacementName..
(comments and "\n\n"..comments or "")
)
end
end
end