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.

567 lines
18 KiB

4 years ago
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster_AuctionDB --
-- http://www.curse.com/addons/wow/tradeskillmaster_auctiondb --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- register this file with Ace Libraries
local TSM = select(2, ...)
TSM = LibStub("AceAddon-3.0"):NewAddon(TSM, "TSM_AuctionDB", "AceEvent-3.0", "AceConsole-3.0")
local AceGUI = LibStub("AceGUI-3.0") -- load the AceGUI libraries
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster_AuctionDB") -- loads the localization table
TSM.MAX_AVG_DAY = 1
local SECONDS_PER_DAY = 60 * 60 * 24
local savedDBDefaults = {
realm = {
4 years ago
appData = {},
scanData = "",
time = 0,
lastCompleteScan = 0,
lastScanSecondsPerPage = -1,
4 years ago
appDataUpdate = 0,
},
profile = {
tooltip = true,
resultsPerPage = 50,
resultsSortOrder = "ascending",
resultsSortMethod = "name",
hidePoorQualityItems = true,
marketValueTooltip = true,
minBuyoutTooltip = true,
showAHTab = true,
},
}
-- Called once the player has loaded WOW.
function TSM:OnInitialize()
-- load the savedDB into TSM.db
TSM.db = LibStub:GetLibrary("AceDB-3.0"):New("AscensionTSM_AuctionDB", savedDBDefaults, true)
4 years ago
-- make easier references to all the modules
for moduleName, module in pairs(TSM.modules) do
TSM[moduleName] = module
end
-- register this module with TSM
TSM:RegisterModule()
TSM.db.realm.time = 10 -- because AceDB won't save if we don't do this...
4 years ago
TSM.data = {}
TSM:Deserialize(TSM.db.realm.scanData, TSM.data)
4 years ago
end
-- registers this module with TSM by first setting all fields and then calling TSMAPI:NewModule().
function TSM:RegisterModule()
TSM.priceSources = {
{ key = "DBMarket", label = L["AuctionDB - Market Value"], callback = "GetMarketValue" },
{ key = "DBMinBuyout", label = L["AuctionDB - Minimum Buyout"], callback = "GetMinBuyout" },
}
TSM.icons = {
{ side = "module", desc = "AuctionDB", slashCommand = "auctiondb", callback = "Config:Load", icon = "Interface\\Icons\\Inv_Misc_Platnumdisks" },
}
if TSM.db.profile.showAHTab then
TSM.auctionTab = { callbackShow = "GUI:Show", callbackHide = "GUI:Hide" }
end
TSM.slashCommands = {
{ key = "adbreset", label = L["Resets AuctionDB's scan data"], callback = "Reset" },
}
TSM.moduleAPIs = {
{ key = "lastCompleteScan", callback = TSM.GetLastCompleteScan },
{ key = "lastCompleteScanTime", callback = TSM.GetLastCompleteScanTime },
{ key = "adbScans", callback = TSM.GetScans },
{ key = "processScanData", callback = "Data:ProcessExternalScanData" },
--{ key = "adbOppositeFaction", callback = TSM.GetOppositeFactionData },
4 years ago
}
TSM.tooltipOptions = {callback = "Config:LoadTooltipOptions"}
TSMAPI:NewModule(TSM)
end
function TSM:LoadAuctionData()
local function LoadDataThread(self, itemIDs)
-- process new items first
for itemID in pairs(TSM.db.realm.appData) do
4 years ago
if not TSM.data[itemID] then
TSM:DecodeItemData(itemID)
TSM:ProcessAppData(itemID)
TSM:EncodeItemData(itemID)
end
self:Yield()
end
local currentDay = TSM.Data:GetDay()
for _, itemID in ipairs(itemIDs) do
TSM:DecodeItemData(itemID)
TSM:ProcessAppData(itemID)
if type(TSM.data[itemID].scans) == "table" then
local temp = {}
for i=0, 14 do
if i <= TSM.MAX_AVG_DAY then
temp[currentDay-i] = TSM.Data:ConvertScansToAvg(TSM.data[itemID].scans[currentDay-i])
else
local dayScans = TSM.data[itemID].scans[currentDay-i]
if type(dayScans) == "table" then
if dayScans.avg then
temp[currentDay-i] = dayScans.avg
else
-- old method
temp[currentDay-i] = TSM.Data:GetAverage(dayScans)
end
elseif type(dayScans) == "number" then
temp[currentDay-i] = dayScans
end
end
end
TSM.data[itemID].scans = temp
end
TSM:EncodeItemData(itemID)
self:Yield()
end
end
local itemIDs = {}
for itemID in pairs(TSM.data) do
tinsert(itemIDs, itemID)
end
TSMAPI.Threading:Start(LoadDataThread, 0.1, nil, itemIDs)
end
function TSM:ProcessAppData(itemID)
if not TSM.db.realm.appData[itemID] then return end
4 years ago
TSM.data[itemID] = TSM.data[itemID] or {scans = {}, lastScan = 0}
local dbData = TSM.data[itemID]
local day = TSM.Data:GetDay()
for _, appData in ipairs(TSM.db.realm.appData[itemID]) do
4 years ago
local marketValue, minBuyout, scanTime = appData.m, appData.b, appData.t
if abs(day - TSM.Data:GetDay(scanTime)) <= TSM.MAX_AVG_DAY then
local dayScans = dbData.scans
dayScans[day] = dayScans[day] or {avg=0, count=0}
if type(dayScans[day]) == "number" then
-- this should never happen...
dayScans[day] = {dayScans[day]}
end
dayScans[day].avg = dayScans[day].avg or 0
dayScans[day].count = dayScans[day].count or 0
if #dayScans[day] > 0 then
dayScans[day] = TSM.Data:ConvertScansToAvg(dayScans[day])
end
dayScans[day].avg = floor((dayScans[day].avg * dayScans[day].count + marketValue) / (dayScans[day].count + 1) + 0.5)
dayScans[day].count = dayScans[day].count + 1
if not dbData.lastScan or dbData.lastScan < scanTime then
dbData.lastScan = scanTime
dbData.minBuyout = minBuyout > 0 and minBuyout or nil
end
end
end
TSM.Data:UpdateMarketValue(dbData)
TSM.db.realm.appData[itemID] = nil
4 years ago
end
function TSM:OnEnable()
local function DecodeJSON(data)
print(data)
4 years ago
data = gsub(data, ":", "=")
data = gsub(data, "\"horde\"", "horde")
data = gsub(data, "\"alliance\"", "alliance")
data = gsub(data, "\"m\"", "m")
data = gsub(data, "\"n\"", "n")
data = gsub(data, "\"b\"", "b")
data = gsub(data, "\"([0-9]+)\"", "[%1]")
loadstring("TSM_APP_DATA_TMP = " .. data .. "")()
local val = TSM_APP_DATA_TMP
TSM_APP_DATA_TMP = nil
return val
end
if TSM.AppData then
local realm = strlower(GetRealmName() or "")
local faction = strlower(UnitFactionGroup("player") or "")
if faction == "" or faction == "Neutral" then return end
local numNewScans = 0
local maxScanTime = 0
for realmInfo, appScanData in pairs(TSM.AppData) do
local r, f, t, extra = ("-"):split(realmInfo)
if extra then
r = r .. "-" .. f
f = t
t = extra
end
r = strlower(r)
f = strlower(f)
local scanTime = tonumber(t)
if realm == r and (faction == f or f == "both") and scanTime > TSM.db.realm.appDataUpdate and abs(TSM.Data:GetDay() - TSM.Data:GetDay(scanTime)) <= TSM.MAX_AVG_DAY then
4 years ago
local importData = DecodeJSON(appScanData)[faction]
if importData then
for itemID, data in pairs(importData) do
itemID = tonumber(itemID)
data.m = tonumber(data.m)
data.b = tonumber(data.b)
data.t = scanTime
if itemID and data.m and data.b then
TSM.db.realm.appData[itemID] = TSM.db.realm.appData[itemID] or {}
tinsert(TSM.db.realm.appData[itemID], data)
4 years ago
end
end
maxScanTime = max(maxScanTime, scanTime)
numNewScans = numNewScans + 1
end
end
end
if numNewScans > 0 then
TSM.db.realm.appDataUpdate = maxScanTime
TSM.db.realm.lastCompleteScan = TSM.db.realm.appDataUpdate
4 years ago
TSM:Printf(L["Imported %s scans worth of new auction data!"], numNewScans)
end
TSM.AppData = nil
end
TSM:LoadAuctionData()
end
function TSM:OnTSMDBShutdown()
TSM.db.realm.time = 0
4 years ago
TSM:Serialize(TSM.data)
end
function TSM:GetTooltip(itemString, quantity)
if not TSM.db.profile.tooltip then return end
if not strfind(itemString, "item:") then return end
local itemID = TSMAPI:GetItemID(itemString)
if not itemID or not TSM.data[itemID] then return end
local text = {}
local moneyCoinsTooltip = TSMAPI:GetMoneyCoinsTooltip()
quantity = quantity or 1
-- add market value info
if TSM.db.profile.marketValueTooltip then
local marketValue = TSM:GetMarketValue(itemID)
if marketValue then
if moneyCoinsTooltip then
if IsShiftKeyDown() then
tinsert(text, { left = " " .. format(L["Market Value x%s:"], quantity), right = TSMAPI:FormatTextMoneyIcon(marketValue * quantity, "|cffffffff", true) })
else
tinsert(text, { left = " " .. L["Market Value:"], right = TSMAPI:FormatTextMoneyIcon(marketValue, "|cffffffff", true) })
end
else
if IsShiftKeyDown() then
tinsert(text, { left = " " .. format(L["Market Value x%s:"], quantity), right = TSMAPI:FormatTextMoney(marketValue * quantity, "|cffffffff", true) })
else
tinsert(text, { left = " " .. L["Market Value:"], right = TSMAPI:FormatTextMoney(marketValue, "|cffffffff", true) })
end
end
end
end
-- add min buyout info
if TSM.db.profile.minBuyoutTooltip then
local minBuyout = TSM:GetMinBuyout(itemID)
if minBuyout then
if quantity then
if moneyCoinsTooltip then
if IsShiftKeyDown() then
tinsert(text, { left = " " .. format(L["Min Buyout x%s:"], quantity), right = TSMAPI:FormatTextMoneyIcon(minBuyout * quantity, "|cffffffff", true) })
else
tinsert(text, { left = " " .. L["Min Buyout:"], right = TSMAPI:FormatTextMoneyIcon(minBuyout, "|cffffffff", true) })
end
else
if IsShiftKeyDown() then
tinsert(text, { left = " " .. format(L["Min Buyout x%s:"], quantity), right = TSMAPI:FormatTextMoney(minBuyout * quantity, "|cffffffff", true) })
else
tinsert(text, { left = " " .. L["Min Buyout:"], right = TSMAPI:FormatTextMoney(minBuyout, "|cffffffff", true) })
end
end
end
end
end
-- add heading and last scan time info
if #text > 0 then
local lastScan = TSM:GetLastScanTime(itemID)
if lastScan then
local timeColor = "|cffff0000"
if (time() - lastScan) < 60 * 60 * 3 then
timeColor = "|cff00ff00"
elseif (time() - lastScan) < 60 * 60 * 12 then
timeColor = "|cffffff00"
end
local timeDiff = SecondsToTime(time() - lastScan)
--tinsert(text, 1, { left = "|cffffff00" .. "TSM AuctionDB:", right = "|cffffffff" .. format(L["%s ago"], timeDiff) })
tinsert(text, 1, { left = "|cffffff00" .. "TSM AuctionDB:", right = format("%s (%s)", format("|cffffffff".."%d auctions".."|r", TSM.data[itemID].quantity), format(timeColor..L["%s ago"].."|r", timeDiff)) })
else
tinsert(text, 1, { left = "|cffffff00" .. "TSM AuctionDB:", right = "|cffffffff" .. L["Not Scanned"] })
end
return text
end
end
function TSM:Reset()
-- Popup Confirmation Window used in this module
StaticPopupDialogs["TSMAuctionDBClearDataConfirm"] = StaticPopupDialogs["TSMAuctionDBClearDataConfirm"] or {
text = L["Are you sure you want to clear your AuctionDB data?"],
button1 = YES,
button2 = CANCEL,
timeout = 0,
whileDead = true,
hideOnEscape = true,
OnAccept = function()
TSM.db.realm.lastCompleteScan = 0
TSM.db.realm.appDataUpdate = 0
4 years ago
for i in pairs(TSM.data) do
TSM.data[i] = nil
end
TSM:Print(L["Reset Data"])
end,
OnCancel = false,
}
StaticPopup_Show("TSMAuctionDBClearDataConfirm")
for i = 1, 10 do
local popup = _G["StaticPopup" .. i]
if popup and popup.which == "TSMAuctionDBClearDataConfirm" then
popup:SetFrameStrata("TOOLTIP")
break
end
end
end
local alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_="
local base = #alpha
local alphaTable = {}
local alphaTableLookup = {}
for i = 1, base do
local char = strsub(alpha, i, i)
tinsert(alphaTable, char)
alphaTableLookup[char] = i
end
local function decode(h)
if not h then return end
if strfind(h, "~") then return end
local result = 0
local len = #h
for j=len-1, 0, -1 do
if not alphaTableLookup[strsub(h, len-j, len-j)] then error(h.." at index "..len-j) end
result = result + (alphaTableLookup[strsub(h, len-j, len-j)] - 1) * (base ^ j)
j = j - 1
end
return result
end
local function encode(d)
d = tonumber(d)
if not d or not (d < math.huge and d > 0) then -- this cannot be simplified since 0/0 is neither less than nor greater than any number
return "~"
end
local r = d % base
local diff = d - r
if diff == 0 then
return alphaTable[r + 1]
else
return encode(diff / base) .. alphaTable[r + 1]
end
end
local function encodeScans(scans)
local tbl, tbl2 = {}, {}
for day, data in pairs(scans) do
if type(data) == "table" and data.count and data.avg then
-- New method of encoding scans.
4 years ago
data = encode(data.avg).."@"..encode(data.count)
elseif type(data) == "table" then
-- Old method of encoding scans.
4 years ago
for i = 1, #data do
tbl2[i] = encode(data[i])
end
data = table.concat(tbl2, ";", 1, #data)
else
data = encode(data)
end
tinsert(tbl, encode(day) .. ":" .. data)
end
return table.concat(tbl, "!")
end
local function decodeScans(rope)
if rope == "A" then return end
local scans = {}
local days = {("!"):split(rope)}
local currentDay = TSM.Data:GetDay()
for _, data in ipairs(days) do
local day, marketValueData = (":"):split(data)
-- BUG FIXED: Guard against incorrectly encoded "day" or "marketValueData",
-- which can happen extremely rarely due to some very rare, random bug
-- somewhere else in TSM (or perhaps due to mixing different versions
-- of TSM data). The cause of the rare corruption hasn't been found.
-- NOTE: We simply skip any "days/market values" that cannot be decoded,
-- which thereby ensures that we get a cleaned-up "decode" of the data,
-- so that TSM will then write the fixed data when it next "re-encodes"
-- the "decoded in-memory representation" of this item's data!
if day ~= nil and day ~= "" and marketValueData ~= nil and marketValueData ~= "" then
day = decode(day)
-- BUG FIXED: Verify yet again that the day itself was properly decoded,
-- but this time only check for "nil" which indicates "decode()" failure.
if day ~= nil then
-- Create a "scans" table entry for the decoded day.
scans[day] = {}
if strfind(marketValueData, "@") then
-- New method of decoding scans.
local avg, count = ("@"):split(marketValueData)
avg = decode(avg)
count = decode(count)
if avg ~= "~" and count ~= "~" then
if abs(currentDay - day) <= TSM.MAX_AVG_DAY then
scans[day].avg = avg
scans[day].count = count
else
scans[day] = avg
end
4 years ago
end
else
-- Old method of decoding scans.
for _, value in ipairs({(";"):split(marketValueData)}) do
local decodedValue = decode(value)
if decodedValue ~= "~" then
tinsert(scans[day], tonumber(decodedValue))
end
end
if day ~= currentDay then
scans[day] = TSM.Data:GetAverage(scans[day])
4 years ago
end
end
end
end
end
return scans
end
function TSM:Serialize()
local results = {}
for itemID, data in pairs(TSM.data) do
if not data.encoded then
-- should never get here, but just in-case
TSM:EncodeItemData(itemID)
end
if data.encoded then
tinsert(results, "?" .. encode(itemID) .. "," .. data.encoded)
end
end
TSM.db.realm.scanData = table.concat(results)
4 years ago
end
function TSM:Deserialize(data, resultTbl, fullyDecode)
if strsub(data, 1, 1) ~= "?" then return end
for k, a, b, c, d, e, f in gmatch(data, "?([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),([^?]+)") do
local itemID = decode(k)
resultTbl[itemID] = {encoded=strjoin(",", a, b, c, d, e, f)}
if fullyDecode then
TSM:DecodeItemData(itemID, resultTbl)
end
end
end
function TSM:EncodeItemData(itemID, tbl)
tbl = tbl or TSM.data
local data = tbl[itemID]
if data and data.marketValue then
data.encoded = strjoin(",", encode(0), encode(data.marketValue), encode(data.lastScan), encode(0), encode(data.minBuyout), encodeScans(data.scans), encode(data.quantity))
end
end
function TSM:DecodeItemData(itemID, tbl)
tbl = tbl or TSM.data
local data = tbl[itemID]
if data and data.encoded and not data.marketValue then
local a, b, c, d, e, f, g = (","):split(data.encoded)
data.marketValue = decode(b)
data.lastScan = decode(c)
data.minBuyout = decode(e)
data.scans = decodeScans(f)
data.quantity = decode(g)
end
end
function TSM:GetLastCompleteScan()
local lastScan = {}
for itemID, data in pairs(TSM.data) do
TSM:DecodeItemData(itemID)
if data.lastScan == TSM.db.realm.lastCompleteScan then
4 years ago
lastScan[itemID] = { marketValue = data.marketValue, minBuyout = data.minBuyout }
end
end
return lastScan
end
function TSM:GetLastCompleteScanTime()
return TSM.db.realm.lastCompleteScan
4 years ago
end
function TSM:GetScans(link)
if not link then return end
link = select(2, GetItemInfo(link))
if not link then return end
local itemID = TSMAPI:GetItemID(link)
if not TSM.data[itemID] then return end
TSM:DecodeItemData(itemID)
return CopyTable(TSM.data[itemID].scans)
end
function TSM:GetOppositeFactionData()
local realm = GetRealmName()
local faction = "Ascension" -- UnitFactionGroup("player")
4 years ago
if faction == "Horde" then
faction = "Alliance"
elseif faction == "Alliance" then
faction = "Horde"
else
return
end
local data = TSM.db.sv.realm[faction .. " - " .. realm]
4 years ago
if not data or type(data.scanData) ~= "string" then return end
local result = {}
TSM:Deserialize(data.scanData, result, true)
return result
end
function TSM:GetMarketValue(itemID)
if itemID and not tonumber(itemID) then
itemID = TSMAPI:GetItemID(itemID)
end
if not itemID or not TSM.data[itemID] then return end
TSM:DecodeItemData(itemID)
if not TSM.data[itemID].marketValue or TSM.data[itemID].marketValue == 0 then
TSM.data[itemID].marketValue = TSM.Data:GetMarketValue(TSM.data[itemID].scans)
end
return TSM.data[itemID].marketValue ~= 0 and TSM.data[itemID].marketValue or nil
end
function TSM:GetLastScanTime(itemID)
TSM:DecodeItemData(itemID)
return itemID and TSM.data[itemID].lastScan
end
function TSM:GetMinBuyout(itemID)
if itemID and not tonumber(itemID) then
itemID = TSMAPI:GetItemID(itemID)
end
if not itemID or not TSM.data[itemID] then return end
TSM:DecodeItemData(itemID)
return TSM.data[itemID].minBuyout
end