-- CoaExporter / Collectors / MysticScrolls.lua -- -- Async tooltip scraper for all known mystic enchant scrolls. -- Iterates AE.ScrollCatalog (generated from AtlasLootAscension) and dumps -- tooltip text + the spell taught by each scroll. -- -- Usage: /coae scrolls scan → start scan (one-time; dumps progress to chat) -- /coae scrolls export → print accumulated JSON of everything scanned -- /coae scrolls reset → clear cache -- -- Design notes: -- - WoW lazy-loads item data. First GetItemInfo() for an unseen item -- returns nil and triggers a server query. We retry with delay. -- - Results accumulate in CoaExporterScrollCache SavedVariable so a -- single run across sessions builds up the full DB. -- - A "scan" pass fires a batch of GetItemInfo requests, then waits, -- then re-scans and records whatever resolved. Unresolved entries are -- retried on the next scan. CoaExporter = _G.CoaExporter or {} _G.CoaExporter = CoaExporter local AE = CoaExporter CoaExporterScrollCache = CoaExporterScrollCache or { entries = {}, meta = { lastScanAt = nil, totalCatalog = 0, resolved = 0, unresolved = {}, }, } local function EnsureScanner() if not AE._scrollScanner then AE._scrollScanner = CreateFrame("GameTooltip", "CoaExpScrollsTT", nil, "GameTooltipTemplate") AE._scrollScanner:SetOwner(WorldFrame, "ANCHOR_NONE") end return AE._scrollScanner end local function TipLines(tt) local out = {} for i = 1, tt:NumLines() do local left = _G[tt:GetName() .. "TextLeft" .. i] if left then local t = left:GetText() if t and t ~= "" then out[#out+1] = t end end end return out end local function ScanItemTooltip(itemID) local tt = EnsureScanner() tt:ClearLines() tt:SetHyperlink("item:" .. itemID) return TipLines(tt) end local function FlatCatalog() local out = {} if not AE.ScrollCatalog then return out end for class, list in pairs(AE.ScrollCatalog) do for _, e in ipairs(list) do out[#out+1] = { itemID = e.itemID, name = e.name, class = class } end end return out end local function TryResolve(entry) local cache = CoaExporterScrollCache.entries if cache[entry.itemID] and cache[entry.itemID].itemTooltip then return true end local name, _, quality = GetItemInfo(entry.itemID) if not name then GameTooltip:SetOwner(WorldFrame, "ANCHOR_NONE") GameTooltip:SetHyperlink("item:" .. entry.itemID) GameTooltip:Hide() return false end local itemLines = ScanItemTooltip(entry.itemID) local spellName, spellRank if GetItemSpell then spellName, spellRank = GetItemSpell(entry.itemID) end cache[entry.itemID] = { itemID = entry.itemID, class = entry.class, scrollName = entry.name, itemName = name, quality = quality, spellName = spellName, spellRank = spellRank, itemTooltip = table.concat(itemLines, "\n"), fetchedAt = time(), } return #itemLines > 0 end function AE.ScrollsScanPass() local catalog = FlatCatalog() local total = #catalog CoaExporterScrollCache.meta.totalCatalog = total local newlyResolved = 0 local unresolved = {} for _, entry in ipairs(catalog) do if TryResolve(entry) then newlyResolved = newlyResolved + 1 else unresolved[#unresolved+1] = entry.itemID end end CoaExporterScrollCache.meta.lastScanAt = time() CoaExporterScrollCache.meta.resolved = newlyResolved CoaExporterScrollCache.meta.unresolved = unresolved return { total = total, resolved = newlyResolved, unresolved = #unresolved } end function AE.ScrollsStartScan(callback) local attempts = 0 local maxAttempts = 4 local function step() attempts = attempts + 1 local stats = AE.ScrollsScanPass() DEFAULT_CHAT_FRAME:AddMessage(string.format( "CoaExporter scrolls: pass %d/%d - resolved %d/%d (still unresolved: %d)", attempts, maxAttempts, stats.resolved, stats.total, stats.unresolved)) if stats.unresolved > 0 and attempts < maxAttempts then local t = CreateFrame("Frame") local elapsed = 0 t:SetScript("OnUpdate", function(self, dt) elapsed = elapsed + dt if elapsed > 5 then self:SetScript("OnUpdate", nil) step() end end) else if callback then callback(stats) end end end step() end function AE.ScrollsExport() local out = { schemaVersion = 1, exportedAt = date("!%Y-%m-%dT%H:%M:%SZ"), catalogTotal = AE.ScrollCatalogTotal or 0, cacheMeta = CoaExporterScrollCache.meta, entries = {}, } for _, rec in pairs(CoaExporterScrollCache.entries) do table.insert(out.entries, rec) end return out end function AE.ScrollsReset() CoaExporterScrollCache = { entries = {}, meta = { lastScanAt = nil, totalCatalog = 0, resolved = 0, unresolved = {} }, } end -- Compute total once on load (used in /coae catalog status etc.) do local total = 0 if AE.ScrollCatalog then for _, list in pairs(AE.ScrollCatalog) do total = total + #list end end AE.ScrollCatalogTotal = total end AE._loadedMysticScrolls = true