local _, addon = ... local DataProvider = {}; addon.PerksProgramDataProvider = DataProvider; local PerksProgramAPI = C_PerksProgram; local C_TransmogCollection = C_TransmogCollection; if PerksProgramAPI then DataProvider.programExisted = true; else PerksProgramAPI = {}; end local CURRENCY_ID = Constants.CurrencyConsts.CURRENCY_ID_PERKS_PROGRAM_DISPLAY_INFO or 2032; local GetVendorItemInfo = PerksProgramAPI.GetVendorItemInfo; local GetTimeRemaining = PerksProgramAPI.GetTimeRemaining; local GetCurrencyAmount = PerksProgramAPI.GetCurrencyAmount; --Returns the amount of Trader's Tendor (2032) Constants.CurrencyConsts.CURRENCY_ID_PERKS_PROGRAM_DISPLAY_INFO local GetCurrentCalendarTime = C_DateAndTime.GetCurrentCalendarTime; local type = type; local VendorItemDataCache = {}; local DB; local IGNORED_KEYS = { purchased = true, perksVendorItemID = true, timeRemaining = true, refundable = true, pending = true, }; local DELIMITER = "::"; local function CompressTable(tbl) local output = DELIMITER; local valueType; for k, v in pairs(tbl) do if not IGNORED_KEYS[k] then valueType = type(v); if valueType == "number" or valueType == "string" and v ~= 0 and v ~= "" then output = output ..k ..DELIMITER.. v ..DELIMITER; end end end return output end local function DecompressData(dataString) local key, value, seg; local numericVal; local tonumber = tonumber; local find = string.find; local strsub = string.sub; local tbl = {}; local isKey = true; local stringLen = string.len(dataString); local initPos = 3; local fromIndex, toIndex = find(dataString, DELIMITER, initPos); while fromIndex and toIndex do seg = strsub(dataString, initPos, fromIndex - 1); initPos = toIndex + 1; fromIndex, toIndex = find(dataString, DELIMITER, initPos); if isKey then key = seg; else value = seg; numericVal = tonumber(value); if numericVal then tbl[key] = numericVal; else tbl[key] = value; end end isKey = not isKey; end return tbl end function DataProvider:DoesPerksProgramExist() return self.programExisted end function DataProvider:IsNewVendorItem(vendorItemID) return vendorItemID and DB.VendorItems[vendorItemID] == nil end function DataProvider:SaveVendorItemData(vendorItemID, overwrite) if vendorItemID and (overwrite or self:IsNewVendorItem(vendorItemID)) then local info = GetVendorItemInfo(vendorItemID); if info and info.perksVendorItemID and info.perksVendorItemID ~= 0 then if info.name == "" or info.description == "" then return false end local dataString = CompressTable(info); local month, year = self:GetActivePerksDate(); local date = year.."/"..month; dataString = dataString .. "addedDate" ..DELIMITER.. date ..DELIMITER; VendorItemDataCache[vendorItemID] = info; info.addedInYear = year; info.addedInMonth = month; DB.VendorItems[vendorItemID] = dataString; return dataString end end end function DataProvider:GetVendorItemInfoFromDatabase(vendorItemID) local info; if DB.VendorItems[vendorItemID] then info = DecompressData( DB.VendorItems[vendorItemID] ); info.isCache = true; return info end end function DataProvider:GetAndCacheVendorItemInfo(vendorItemID) if not VendorItemDataCache[vendorItemID] then local info = GetVendorItemInfo(vendorItemID); --! After patch ?, the perksVendorItemID will not return 0 even you haven't visited Trading Post during the game session --! So (perksVendorItemID ~= 0) is no longer a reliable way to check if we need to decompress data from our SavedVariables if info and info.perksVendorItemID ~= 0 then if info.name and info.name ~= "" then VendorItemDataCache[vendorItemID] = info; end return info else VendorItemDataCache[vendorItemID] = self:GetVendorItemInfoFromDatabase(vendorItemID); end end return VendorItemDataCache[vendorItemID] end function DataProvider:GetVendorItemName(vendorItemID) local info = self:GetAndCacheVendorItemInfo(vendorItemID); if info then return info.name else return "" end end function DataProvider:GetVendorItemCategory(vendorItemID) local info = self:GetAndCacheVendorItemInfo(vendorItemID); if info then return info.perksVendorCategoryID else return 128 end end function DataProvider:GetVendorItemDescription(vendorItemID) local info = self:GetAndCacheVendorItemInfo(vendorItemID); if info then return info.description else return "" end end function DataProvider:GetVendorItemPrice(vendorItemID) local info = self:GetAndCacheVendorItemInfo(vendorItemID); if info then if info.isCache then return info.price or 0 else local cachedData = self:GetVendorItemInfoFromDatabase(vendorItemID); local price = cachedData and cachedData.price; if price then return price end end end return 0 end function DataProvider:GetVendorItemTransmogSetID(vendorItemID) local info = self:GetAndCacheVendorItemInfo(vendorItemID); if info and info.transmogSetID ~= 0 then return info.transmogSetID else local cachedData = self:GetVendorItemInfoFromDatabase(vendorItemID); return cachedData and cachedData.transmogSetID or nil; end end function DataProvider:GetVendorItemTransmogSourceID(vendorItemID) local info = self:GetAndCacheVendorItemInfo(vendorItemID); if info and info.itemModifiedAppearanceID ~= 0 then return info.itemModifiedAppearanceID else local cachedData = self:GetVendorItemInfoFromDatabase(vendorItemID); if cachedData and cachedData.itemModifiedAppearanceID and cachedData.itemModifiedAppearanceID ~= 0 then return cachedData.itemModifiedAppearanceID end end end function DataProvider:IsVendorItemPurchased(vendorItemID) local info = self:GetAndCacheVendorItemInfo(vendorItemID); if info then if info.isCache or (info.price == 0) then --Player didn't visit Trading Post during this session, use cache --[[ 1 Transmog 2 Mount 3 Pet 5 Toy 7 Illusion 8 Transmogset --]] local itemID = info.itemID; local category = info.perksVendorCategoryID; if category == 1 then local appearanceID; local sourceID = info.itemModifiedAppearanceID; if (not sourceID) or sourceID == 0 then appearanceID, sourceID = C_TransmogCollection.GetItemInfo(itemID); end if sourceID and sourceID ~= 0 then return C_TransmogCollection.PlayerKnowsSource(sourceID); end elseif category == 2 then local mountID = C_MountJournal.GetMountFromItem(itemID); if mountID then local isCollected = select(11, C_MountJournal.GetMountInfoByID(mountID)); return isCollected end elseif category == 3 then local petName = C_PetJournal.GetPetInfoByItemID(itemID); if petName then local _, petGUID = C_PetJournal.FindPetIDByName(petName); return petGUID ~= nil end elseif category == 5 then return PlayerHasToy(itemID); elseif category == 7 then local illusionID = info.illusionID; if illusionID then local tbl = C_TransmogCollection.GetIllusionInfo(illusionID); return tbl and tbl.isCollected end elseif category == 8 then local transmogSetID = info.transmogSetID; if transmogSetID == 0 then local itemInfo = DataProvider:GetVendorItemInfoFromDatabase(vendorItemID); transmogSetID = itemInfo and itemInfo.transmogSetID; end if transmogSetID then local sourceIDs = C_TransmogSets.GetAllSourceIDs(transmogSetID); if sourceIDs and sourceIDs[1] then return C_TransmogCollection.PlayerKnowsSource( sourceIDs[1] ); end end end end return info.purchased end end function DataProvider:GetVendorItemPriceBySourceID(sourceID) local vendorItemIDs = self:GetCurrentMonthItems(); if vendorItemIDs then for _, vendorItemID in ipairs(vendorItemIDs) do if self:GetVendorItemTransmogSourceID(vendorItemID) == sourceID then return self:GetVendorItemPrice(vendorItemID), self:IsVendorItemPurchased(vendorItemID) end end end end function DataProvider:UpdateActivePerksMonthInfo() if not (self.month and self.currentMonthName) then local activitiesInfo = C_PerksActivities.GetPerksActivitiesInfo(); local month = activitiesInfo.activePerksMonth; local currentCalendarTime = GetCurrentCalendarTime(); local year = currentCalendarTime.year; self.currentMonthName = activitiesInfo.displayMonthName; self.year = year; self.month = month; for _, monthInfo in ipairs(DB.MonthNames) do if month == monthInfo.m and year == monthInfo.y then return end end table.insert(DB.MonthNames, { y = year, m = month, n = activitiesInfo.displayMonthName, }); end end function DataProvider:GetActivePerksDate() self:UpdateActivePerksMonthInfo(); return self.month, self.year end function DataProvider:GetCurrentDisplayMonthName() self:UpdateActivePerksMonthInfo(); return self.currentMonthName; end function DataProvider:GetDisplayMonthName(year, month) if type(year) == "string" then year, month = string.match(year, "(%d+)/(%d+)"); year = tonumber(year); month = tonumber(month); end if not self.monthNames then --[year] = {[month] = name}, self.monthNames = {}; for _, monthInfo in ipairs(DB.MonthNames) do if not self.monthNames[monthInfo.y] then self.monthNames[monthInfo.y] = {}; end self.monthNames[monthInfo.y][monthInfo.m] = monthInfo.n; end end if not self.monthNames[year] then self.monthNames[year] = {}; end if not self.monthNames[year][month] then self.monthNames[year][month] = self:GetCurrentDisplayMonthName(); end return self.monthNames[year][month] end local EventListener = CreateFrame("Frame"); if DataProvider:DoesPerksProgramExist() then EventListener:RegisterEvent("PERKS_ACTIVITIES_UPDATED"); EventListener:RegisterEvent("PERKS_ACTIVITY_COMPLETED"); EventListener:RegisterEvent("PERKS_PROGRAM_DATA_REFRESH"); EventListener:RegisterEvent("PERKS_PROGRAM_CURRENCY_REFRESH"); EventListener:RegisterEvent("PERKS_PROGRAM_REFUND_SUCCESS"); EventListener:RegisterEvent("PERKS_PROGRAM_PURCHASE_SUCCESS"); end EventListener:SetScript("OnEvent", function(self, event, ...) if event == "PERKS_ACTIVITIES_UPDATED" or event == "PERKS_ACTIVITY_COMPLETED" then DataProvider:ClearActivityCache(); elseif event == "PERKS_PROGRAM_DATA_REFRESH" then local vendorItemIDs = C_PerksProgram.GetAvailableVendorItemIDs(); for _, vendorItemID in pairs(vendorItemIDs) do GetVendorItemInfo(vendorItemID); end C_Timer.After(1, function() for _, vendorItemID in pairs(vendorItemIDs) do GetVendorItemInfo(vendorItemID); DataProvider:SaveVendorItemData(vendorItemID); end end); DataProvider:UpdateActivePerksMonthInfo(); if not self.monthItemUpdated then self.monthItemUpdated = true; local forceUpdate = true; DataProvider:SaveCurrentMonthItems(vendorItemIDs, forceUpdate); end elseif event == "PERKS_PROGRAM_CURRENCY_REFRESH" then DataProvider.ownedCurrencyAmount = nil; elseif event == "PERKS_PROGRAM_REFUND_SUCCESS" or event == "PERKS_PROGRAM_PURCHASE_SUCCESS" then local vendorItemID = ...; local isOwned = event == "PERKS_PROGRAM_PURCHASE_SUCCESS"; if vendorItemID and VendorItemDataCache[vendorItemID] then VendorItemDataCache[vendorItemID].purchased = isOwned; VendorItemDataCache[vendorItemID].refundable = isOwned; end DataProvider.ownedCurrencyAmount = nil; end end); function DataProvider:ClearActivityCache() self.unclaimedPoints = nil; self.unearnedPoints = nil; self.currentMonthName = nil; self.month = nil; end function DataProvider:CacheActivityInfo() if not self.unclaimedPoints then local pendingRewards = PerksProgramAPI.GetPendingChestRewards(); local numUnclaimed = 0; if pendingRewards then for _, reward in pairs(pendingRewards) do numUnclaimed = numUnclaimed + (reward.rewardAmount or 0); end end self.unclaimedPoints = numUnclaimed; end if not self.unearnedPoints then local activitiesInfo = C_PerksActivities.GetPerksActivitiesInfo(); local currentPoints = 0; local numUnearned = 0; for _, activityInfo in pairs(activitiesInfo.activities) do if activityInfo.completed then currentPoints = currentPoints + activityInfo.thresholdContributionAmount; end end for _, thresholdInfo in pairs(activitiesInfo.thresholds) do if thresholdInfo.requiredContributionAmount > currentPoints then numUnearned = numUnearned + thresholdInfo.currencyAwardAmount; end end self.unearnedPoints = numUnearned; end end function DataProvider:GetAvailableCurrency() --Returns the amounts of unclaimed and unearned token self:CacheActivityInfo(); return self.unclaimedPoints or 0, self.unearnedPoints or 0; end function DataProvider:GetCurrencyAmount() if not self.ownedCurrencyAmount then self.ownedCurrencyAmount = GetCurrencyAmount() or 0; end return self.ownedCurrencyAmount end function DataProvider:SaveCurrentMonthItems(vendorItemIDs, forceUpdate) --Sometimes not all items are immediately available on day 1 local month = self:GetActivePerksDate(); if month ~= DB.CurrentMonthData.month or forceUpdate then local tbl = {}; local total = 0; for i, id in ipairs(vendorItemIDs) do if id ~= 0 then total = total + 1; tbl[total] = id; end end DB.CurrentMonthData.items = tbl; DB.CurrentMonthData.month = month; end end function DataProvider:GetCurrentMonthItems() local month = self:GetActivePerksDate(); if month and month == DB.CurrentMonthData.month then return DB.CurrentMonthData.items else DB.CurrentMonthData = {}; end end function DataProvider:GetVendorItemAddedMonthName(vendorItemID) local info = self:GetVendorItemInfoFromDatabase(vendorItemID); if info and info.addedDate then if not self.currentMonthDate then local month, year = self:GetActivePerksDate(); self.currentMonthDate = year.."/"..month; end local monthName = DataProvider:GetDisplayMonthName(info.addedDate); return monthName, (self.currentMonthDate == info.addedDate); else return nil, true end end function DataProvider:IsValidItem(vendorItemID) local categoryID = self:GetVendorItemCategory(vendorItemID); return categoryID and categoryID ~=0 and categoryID ~= 128 end do local time = time; local EpochToDate = NarciAPI.EpochToDate; function DataProvider:SaveUserData(dataKey, data) DB[dataKey] = data; end function DataProvider:GetUserData(dataKey) return DB[dataKey] end function DataProvider:SetTimeLimitedData(dataKey, data) --Data saved in DB that expire next month local tbl = {}; tbl.timeChanged = time(); tbl.data = data; self:SaveUserData(dataKey, tbl); end function DataProvider:GetTimeLimitedData(dataKey) local data = self:GetUserData(dataKey); if data then local currentDate = EpochToDate(time()); local oldDate = EpochToDate(data.timeChanged); if currentDate.month == oldDate.month then return data.data end end end end local function LoadDatabase() if not NarcissusDB.PerksProgramDB then NarcissusDB.PerksProgramDB = {}; end DB = NarcissusDB.PerksProgramDB; if not DB.VendorItems then DB.VendorItems = {}; end if not DB.MonthNames then DB.MonthNames = {}; end if not DB.CurrentMonthData then DB.CurrentMonthData = {}; end end addon.AddInitializationCallback(LoadDatabase); --[[ local activitiesInfo = C_PerksActivities.GetPerksActivitiesInfo(); [thresholds] [displayMonthName] Enum.PerksVendorCategoryType 1 Transmog 2 Mount 3 Pet 5 Toy 7 Illusion 8 Transmogset --]]