--[[ Transmisson.lua This file contains all transmission related functionality, e.g. import/export and chat links. For that it hooks into the chat frame and addon message channels. This file adds the following API to the WeakAuras object: DisplayToString(id, forChat) Converts the display id to a plain text string DataToString(id) Converts the display id to a formatted table SerializeTable(data) Converts the table data to a formatted table ShowDisplayTooltip(data, children, icon, icons, import, compressed) Shows a tooltip frame for an aura, which allows for importing if import is true Import(str, [target]) Imports an aura from a table, which may or may not be encoded as a B64 string. If target is installed data, or is a uid which points to installed data, then the import will be an update to that aura ]]-- if not WeakAuras.IsCorrectVersion() then return end local AddonName, Private = ... -- Lua APIs local tinsert = table.insert local tostring, string_char, strsplit = tostring, string.char, strsplit local pairs, type, unpack = pairs, type, unpack local error = error local bit_band, bit_lshift, bit_rshift = bit.band, bit.lshift, bit.rshift local coroutine = coroutine local WeakAuras = WeakAuras; local L = WeakAuras.L; local versionString = WeakAuras.versionString; local regionOptions = WeakAuras.regionOptions; local regionTypes = WeakAuras.regionTypes; -- Local functions local decodeB64, GenerateUniqueID local CompressDisplay, ShowTooltip, TableToString, StringToTable local RequestDisplay, TransmitError, TransmitDisplay local bytetoB64 = { [0]="a","b","c","d","e","f","g","h", "i","j","k","l","m","n","o","p", "q","r","s","t","u","v","w","x", "y","z","A","B","C","D","E","F", "G","H","I","J","K","L","M","N", "O","P","Q","R","S","T","U","V", "W","X","Y","Z","0","1","2","3", "4","5","6","7","8","9","(",")" } local B64tobyte = { a = 0, b = 1, c = 2, d = 3, e = 4, f = 5, g = 6, h = 7, i = 8, j = 9, k = 10, l = 11, m = 12, n = 13, o = 14, p = 15, q = 16, r = 17, s = 18, t = 19, u = 20, v = 21, w = 22, x = 23, y = 24, z = 25, A = 26, B = 27, C = 28, D = 29, E = 30, F = 31, G = 32, H = 33, I = 34, J = 35, K = 36, L = 37, M = 38, N = 39, O = 40, P = 41, Q = 42, R = 43, S = 44, T = 45, U = 46, V = 47, W = 48, X = 49, Y = 50, Z = 51,["0"]=52,["1"]=53,["2"]=54,["3"]=55, ["4"]=56,["5"]=57,["6"]=58,["7"]=59,["8"]=60,["9"]=61,["("]=62,[")"]=63 } -- This code is based on the Encode7Bit algorithm from LibCompress -- Credit goes to Galmok (galmok@gmail.com) local decodeB64Table = {} function decodeB64(str) local bit8 = decodeB64Table; local decoded_size = 0; local ch; local i = 1; local bitfield_len = 0; local bitfield = 0; local l = #str; while true do if bitfield_len >= 8 then decoded_size = decoded_size + 1; bit8[decoded_size] = string_char(bit_band(bitfield, 255)); bitfield = bit_rshift(bitfield, 8); bitfield_len = bitfield_len - 8; end ch = B64tobyte[str:sub(i, i)]; bitfield = bitfield + bit_lshift(ch or 0, bitfield_len); bitfield_len = bitfield_len + 6; if i > l then break; end i = i + 1; end return table.concat(bit8, "", 1, decoded_size) end function GenerateUniqueID() -- generates a unique random 11 digit number in base64 local s = {} for i=1,11 do tinsert(s, bytetoB64[math.random(0, 63)]) end return table.concat(s) end WeakAuras.GenerateUniqueID = GenerateUniqueID local function stripNonTransmissableFields(datum, fieldMap) for k, v in pairs(fieldMap) do if type(v) == "table" and type(datum[k]) == "table" then stripNonTransmissableFields(datum[k], v) elseif v == true then datum[k] = nil end end end function CompressDisplay(data) -- Clean up custom trigger fields that are unused -- Those can contain lots of unnecessary data. -- Also we warn about any custom code, so removing unnecessary -- custom code prevents unnecessary warnings for triggernum, triggerData in ipairs(data.triggers) do local trigger, untrigger = triggerData.trigger, triggerData.untrigger if (trigger and trigger.type ~= "custom") then trigger.custom = nil; trigger.customDuration = nil; trigger.customName = nil; trigger.customIcon = nil; trigger.customTexture = nil; trigger.customStacks = nil; if (untrigger) then untrigger.custom = nil; end end end local copiedData = CopyTable(data) stripNonTransmissableFields(copiedData, Private.non_transmissable_fields) copiedData.tocversion = WeakAuras.BuildInfo return copiedData; end local function filterFunc(_, event, msg, player, l, cs, t, flag, channelId, ...) if flag == "GM" or flag == "DEV" or (event == "CHAT_MSG_CHANNEL" and type(channelId) == "number" and channelId > 0) then return end local newMsg = ""; local remaining = msg; local done; repeat local start, finish, characterName, displayName = remaining:find("%[WeakAuras: ([^%s]+) %- ([^%]]+)%]"); if(characterName and displayName) then characterName = characterName:gsub("|c[Ff][Ff]......", ""):gsub("|r", ""); displayName = displayName:gsub("|c[Ff][Ff]......", ""):gsub("|r", ""); newMsg = newMsg..remaining:sub(1, start-1); newMsg = newMsg.."|Hgarrmission:weakauras|h|cFF8800FF["..characterName.." |r|cFF8800FF- "..displayName.."]|h|r"; remaining = remaining:sub(finish + 1); else done = true; end until(done) if newMsg ~= "" then local trimmedPlayer = Ambiguate(player, "none") if event == "CHAT_MSG_WHISPER" and not UnitInRaid(trimmedPlayer) and not UnitInParty(trimmedPlayer) then -- XXX: Need a guild check local _, num = BNGetNumFriends() for i=1, num do if C_BattleNet then -- introduced in 8.2.5 PTR local toon = C_BattleNet.GetFriendNumGameAccounts(i) for j=1, toon do local gameAccountInfo = C_BattleNet.GetFriendGameAccountInfo(i, j); if gameAccountInfo.characterName == trimmedPlayer and gameAccountInfo.clientProgram == "WoW" then return false, newMsg, player, l, cs, t, flag, channelId, ...; -- Player is a real id friend, allow it end end else -- keep old method for 8.2 and Classic local toon = BNGetNumFriendGameAccounts(i) for j=1, toon do local _, rName, rGame = BNGetFriendGameAccountInfo(i, j) if rName == trimmedPlayer and rGame == "WoW" then return false, newMsg, player, l, cs, t, flag, channelId, ...; -- Player is a real id friend, allow it end end end end return true -- Filter strangers else return false, newMsg, player, l, cs, t, flag, channelId, ...; end end end ChatFrame_AddMessageEventFilter("CHAT_MSG_CHANNEL", filterFunc) ChatFrame_AddMessageEventFilter("CHAT_MSG_YELL", filterFunc) ChatFrame_AddMessageEventFilter("CHAT_MSG_GUILD", filterFunc) ChatFrame_AddMessageEventFilter("CHAT_MSG_OFFICER", filterFunc) ChatFrame_AddMessageEventFilter("CHAT_MSG_PARTY", filterFunc) ChatFrame_AddMessageEventFilter("CHAT_MSG_PARTY_LEADER", filterFunc) ChatFrame_AddMessageEventFilter("CHAT_MSG_RAID", filterFunc) ChatFrame_AddMessageEventFilter("CHAT_MSG_RAID_LEADER", filterFunc) ChatFrame_AddMessageEventFilter("CHAT_MSG_SAY", filterFunc) ChatFrame_AddMessageEventFilter("CHAT_MSG_WHISPER", filterFunc) ChatFrame_AddMessageEventFilter("CHAT_MSG_WHISPER_INFORM", filterFunc) ChatFrame_AddMessageEventFilter("CHAT_MSG_BN_WHISPER", filterFunc) ChatFrame_AddMessageEventFilter("CHAT_MSG_BN_WHISPER_INFORM", filterFunc) ChatFrame_AddMessageEventFilter("CHAT_MSG_INSTANCE_CHAT", filterFunc) ChatFrame_AddMessageEventFilter("CHAT_MSG_INSTANCE_CHAT_LEADER", filterFunc) local buttonAnchor = CreateFrame("FRAME", "WeakAurasTooltipAnchor", ItemRefTooltip) buttonAnchor:SetAllPoints() buttonAnchor:Hide() buttonAnchor:RegisterEvent("PLAYER_REGEN_ENABLED") buttonAnchor:RegisterEvent("PLAYER_REGEN_DISABLED") local thumbnailAnchor = CreateFrame("FRAME", "WeakAurasTooltipThumbnailFrame", buttonAnchor) thumbnailAnchor:SetSize(40,40) thumbnailAnchor:SetPoint("TOPRIGHT", -27, -8) local importButton = CreateFrame("BUTTON", "WeakAurasTooltipImportButton", buttonAnchor, "UIPanelButtonTemplate") importButton:SetPoint("BOTTOMRIGHT", -8, 8) local showCodeButton = CreateFrame("BUTTON", "WeakAurasTooltipImportButton", buttonAnchor, "UIPanelButtonTemplate") showCodeButton:SetPoint("BOTTOMLEFT", 8, 8) showCodeButton:SetText(L["Show Code"]) showCodeButton:SetWidth(90) local urlEditBox = CreateFrame("EDITBOX", "WeakAurasTooltipUrlEditBox", buttonAnchor) urlEditBox:SetWidth(250) urlEditBox:SetHeight(34) urlEditBox:SetFont(STANDARD_TEXT_FONT, 12) urlEditBox:SetPoint("TOPLEFT", 8, -57) urlEditBox:SetScript("OnMouseUp", function() urlEditBox:HighlightText() end) urlEditBox:SetScript("OnChar", function() urlEditBox:SetText(urlEditBox.text) urlEditBox:HighlightText() end) urlEditBox:SetAutoFocus(false) local checkButtons, radioButtons, keyToButton, pendingData = {}, {}, {}, {} for _, key in pairs(Private.internal_fields) do keyToButton[key] = false end for index,category in pairs(Private.update_categories) do local button = CreateFrame("checkButton", "WeakAurasTooltipCheckButton"..index, buttonAnchor, "ChatConfigCheckButtonTemplate") for k, v in pairs(category) do button[k] = v end button.text = _G[button:GetName().."Text"] local point, relativeFrame, relativePoint, xOff, yOff = button.text:GetPoint(1) xOff = xOff + 4 yOff = yOff - 2 button.text:SetPoint(point, relativeFrame, relativePoint, xOff, yOff) button.text:SetText(button.label) button.text:SetTextColor(1, 0.82, 0) button.func = function() if button:GetChecked() then pendingData.buttonsChecked = min(pendingData.buttonsChecked + 1, #checkButtons) else pendingData.buttonsChecked = max(pendingData.buttonsChecked - 1, 0) end radioButtons[#radioButtons]:Click() end checkButtons[index] = button checkButtons[button.name] = button for _, key in pairs(category.fields) do keyToButton[key] = button end end radioButtons = { CreateFrame("CHECKBUTTON", "WeakAurasTooltipRadioButtonCopy", buttonAnchor, "UIRadioButtonTemplate"), -- CreateFrame("CHECKBUTTON", "WeakAurasTooltipRadioButtonReplace", buttonAnchor, "UIRadioButtonTemplate"), CreateFrame("CHECKBUTTON", "WeakAurasTooltipRadioButtonUpdate", buttonAnchor, "UIRadioButtonTemplate"), } local radioButtonLabels = { L["Create a Copy"], L["Update Auras"] } for index, button in ipairs(radioButtons) do button.text = _G[button:GetName().."Text"] button.text:SetFontObject("GameFontNormal") button.text:SetText(radioButtonLabels[index]) button:SetScript("OnClick", function(self) for _, button in ipairs(radioButtons) do button:SetChecked(button == self) end pendingData.mode = index Private.RefreshTooltipButtons() end) end -- Ideally, we would not add an __index method to this -- But that would greatly bloat the update_category for display -- A better solution would be to refactor the data -- Such that all of the display tab settings are in their own subtable setmetatable(keyToButton,{__index = function(_, key) return key and checkButtons.display end,}) local deleted = {} -- magic value local function recurseUpdate(data, chunk) for k,v in pairs(chunk) do if v == deleted then data[k] = nil elseif type(v) == 'table' and type(data[k]) == 'table' then recurseUpdate(data[k], v) else data[k] = v end end end local function Update(data, diff) -- modifies the installed data such that it matches the pending import, sans user-specified options -- diff is expected to be output generated by diff if not diff then WeakAuras.Add(data) return end if not data then return end recurseUpdate(data, diff) WeakAuras.Add(data) return data end local function install(data, oldData, patch, mode, isParent) -- munch the provided data and add, update, or delete as appropriate -- return the data which the SV knows about afterwards (if there is any) local installedUID, imported if mode == 1 then -- always import as a new thing, set preferToUpdate = false if data then imported = CopyTable(data) data.id = WeakAuras.FindUnusedId(data.id) WeakAuras.Add(data) installedUID = data.uid data.preferToUpdate = false if oldData then oldData.preferToUpdate = false end else -- nothing to add return end elseif not oldData then -- this is a new thing if not checkButtons.newchildren:GetChecked() then -- user has chosen to not add new children, so do nothing return end imported = CopyTable(data) data.id = WeakAuras.FindUnusedId(data.id) data.preferToUpdate = true WeakAuras.Add(data) installedUID = data.uid elseif not data then -- this is an old thing if checkButtons.oldchildren:GetChecked() then WeakAuras.Delete(oldData) return else -- user has chosen to not delete obsolete auras, so do nothing return Private.GetDataByUID(oldData.uid) end else -- something to update if patch then -- if there's no result from Diff, then there's nothing to do for key in pairs(patch) do local checkButton = keyToButton[key] if not isParent and checkButton == checkButtons.anchor then checkButton = checkButtons.arrangement end if checkButton and not checkButton:GetChecked() then patch[key] = nil end end if patch.id then patch.id = WeakAuras.FindUnusedId(patch.id) end end WeakAuras.Delete(oldData) if data.uid and data.uid ~= oldData.uid then oldData.uid = data.uid end Update(oldData, patch) oldData.preferToUpdate = true installedUID = oldData.uid imported = data end -- if at this point, then some change has been made in the db. Update History to reflect the change Private.SetHistory(installedUID, imported, "import") return Private.GetDataByUID(installedUID) end local function importPendingData() -- get necessary info local imports, old, diffs = pendingData.newData, pendingData.oldData or {}, pendingData.diffs or {} local addedChildren, deletedChildren = pendingData.added or 0, pendingData.deleted or 0 local mode = pendingData.mode or 1 local indexMap = pendingData.indexMap -- cleanup the mess ItemRefTooltip:Hide()-- this also wipes pendingData via the hook on L521 buttonAnchor:Hide() if thumbnailAnchor.currentThumbnail then regionOptions[thumbnailAnchor.currentThumbnailType].releaseThumbnail(thumbnailAnchor.currentThumbnail) thumbnailAnchor.currentThumbnail = nil end if imports and Private.LoadOptions() then if not WeakAuras.IsOptionsOpen() then WeakAuras.OpenOptions() end else return end WeakAuras.CloseImportExport() WeakAuras.SetImporting(true) -- import parent/single aura local data, oldData, patch = imports[0], old[0], diffs[0] data.parent = nil -- handle sortHybridTable local hybridTables if (data and data.sortHybridTable) or (mode ~= 1 and oldData and oldData.sortHybridTable) then hybridTables = { new = data and data.sortHybridTable or {}, old = mode ~= 1 and oldData and oldData.sortHybridTable or {}, merged = {}, } end if data then data.authorMode = nil end if oldData then oldData.authorMode = nil end local installedData = {[0] = install(data, oldData, patch, mode, true)} WeakAuras.NewDisplayButton(installedData[0]) coroutine.yield() -- handle children, if there are any if #imports > 0 then local parentData = installedData[0] local preserveOldArrangement = mode ~= 1 and not checkButtons.arrangement:GetChecked() local map if not indexMap then map = {} else map = preserveOldArrangement and indexMap.oldToNew or indexMap.newToOld end for i = 1, preserveOldArrangement and #old or #imports do local j = map[i] if preserveOldArrangement then oldData = old[i] if j then data, patch = imports[j], diffs[j] else data, patch = nil, nil end else data, patch = imports[i], diffs[i] if j then oldData = old[j] else oldData = nil end end local oldID, newID = oldData and oldData.id, data and data.id if oldData and mode ~= 1 then oldData.parent = parentData.id end if data then data.parent = parentData.id data.authorMode = nil end if oldData then oldData.authorMode = nil end local childData = install(data, oldData, patch, mode) if childData then tinsert(installedData, childData) if hybridTables then if preserveOldArrangement then hybridTables.merged[childData.id] = oldID and hybridTables.old[oldID] else hybridTables.merged[childData.id] = newID and hybridTables.new[newID] end end end coroutine.yield() end -- now, the other side of the data may have some children which need to be handled if mode ~= 1 then if preserveOldArrangement then if checkButtons.newchildren:GetChecked() and addedChildren > 0 then -- we have children which need to be added local nextInsert, highestInsert, toInsert, newToOld = 1, 0, {}, indexMap.newToOld for newIndex, data in ipairs(imports) do local oldIndex = newToOld[newIndex] if oldIndex then nextInsert = oldIndex + 1 else if nextInsert > highestInsert then highestInsert = nextInsert end toInsert[nextInsert] = toInsert[nextInsert] or {} tinsert(toInsert[nextInsert], data) end coroutine.yield() end for index = highestInsert, 1, -1 do -- go backwards so that tinsert doesn't screw things up if toInsert[index] then for i = #toInsert[index], 1, -1 do local data = toInsert[index][i] local id = data.id data.id = WeakAuras.FindUnusedId(id) data.parent = parentData.id; WeakAuras.Add(data) tinsert(installedData, index, data) if hybridTables and hybridTables.new[id] then hybridTables.merged[data.id] = true end coroutine.yield() end end end end elseif deletedChildren > 0 then if not checkButtons.oldchildren:GetChecked() then -- we have existing children which need migrating -- same algorithm as before, but mirror image local nextInsert, highestInsert, toInsert, oldToNew = 1, 0, {}, indexMap.oldToNew for oldIndex, data in ipairs(old) do local newIndex = oldToNew[oldIndex] if newIndex then nextInsert = newIndex + 1 else if nextInsert > highestInsert then highestInsert = nextInsert end toInsert[nextInsert] = toInsert[nextInsert] or {} tinsert(toInsert[nextInsert], data) end end coroutine.yield() for index = highestInsert, 1, -1 do -- go backwards so that tinsert doesn't screw things up if toInsert[index] then for i = #toInsert[index], 1, -1 do local data = toInsert[index][i] if hybridTables and hybridTables.old[data.id] then hybridTables.merged[data.id] = true end tinsert(installedData, index, data) end end end coroutine.yield() else-- we have children which need deleting local oldToNew = indexMap.oldToNew for oldIndex, oldData in ipairs(old) do if not oldToNew[oldIndex] and WeakAuras.GetData(oldData.id) then WeakAuras.Delete(oldData) coroutine.yield() end end end end end parentData.controlledChildren = {} parentData.sortHybridTable = hybridTables and hybridTables.merged for index, installedChild in ipairs(installedData) do installedChild.parent = parentData.id tinsert(parentData.controlledChildren, installedChild.id) WeakAuras.NewDisplayButton(installedChild) local childButton = WeakAuras.GetDisplayButton(installedChild.id) childButton:SetGroup(parentData.id, parentData.regionType == "dynamicgroup") childButton:SetGroupOrder(index, #parentData.controlledChildren) coroutine.yield() end WeakAuras.Add(parentData); local button = WeakAuras.GetDisplayButton(parentData.id) button.callbacks.UpdateExpandButton() WeakAuras.UpdateGroupOrders(parentData) WeakAuras.UpdateDisplayButton(parentData) WeakAuras.ClearAndUpdateOptions(parentData.id) Private.callbacks:Fire("Import") end WeakAuras.SetImporting(false) return WeakAuras.PickDisplay(installedData[0].id) end ItemRefTooltip:HookScript("OnHide", function(self) buttonAnchor:Hide() wipe(pendingData) if (ItemRefTooltip.WeakAuras_Desc_Box) then ItemRefTooltip.WeakAuras_Desc_Box:Hide(); end end) importButton:SetScript("OnClick", function() Private.dynFrame:AddAction("import", coroutine.create(importPendingData)) end) showCodeButton:SetScript("OnClick", function() WeakAuras.OpenOptions() WeakAuras.OpenCodeReview(pendingData.codes) end) local Compresser = LibStub:GetLibrary("LibCompress") local LibDeflate = LibStub:GetLibrary("LibDeflate") local Serializer = LibStub:GetLibrary("AceSerializer-3.0") local LibSerialize = LibStub("LibSerialize") local Comm = LibStub:GetLibrary("AceComm-3.0") local configForDeflate = {level = 9} -- the biggest bottleneck by far is in transmission and printing; so use maximal compression local configForLS = { errorOnUnserializableType = false } local tooltipLoading; local receivedData; hooksecurefunc("SetItemRef", function(link, text) buttonAnchor:Hide() if(link == "garrmission:weakauras") then local _, _, characterName, displayName = text:find("|Hgarrmission:weakauras|h|cFF8800FF%[([^%s]+) |r|cFF8800FF%- ([^%]]+)%]|h"); if(characterName and displayName) then characterName = characterName:gsub("|c[Ff][Ff]......", ""):gsub("|r", ""); displayName = displayName:gsub("|c[Ff][Ff]......", ""):gsub("|r", ""); if(IsShiftKeyDown()) then local editbox = GetCurrentKeyBoardFocus(); if(editbox) then editbox:Insert("[WeakAuras: "..characterName.." - "..displayName.."]"); end else characterName = characterName:gsub("%.", "") ShowTooltip({ {2, "WeakAuras", displayName, 0.5, 0, 1, 1, 1, 1}, {1, L["Requesting display information from %s ..."]:format(characterName), 1, 0.82, 0}, {1, L["Note, that cross realm transmission is possible if you are on the same group"], 1, 0.82, 0} }); tooltipLoading = true; receivedData = false; RequestDisplay(characterName, displayName); WeakAuras.timer:ScheduleTimer(function() if (tooltipLoading and not receivedData and ItemRefTooltip:IsVisible()) then ShowTooltip({ {2, "WeakAuras", displayName, 0.5, 0, 1, 1, 1, 1}, {1, L["Error not receiving display information from %s"]:format(characterName), 1, 0, 0}, {1, L["Note, that cross realm transmission is possible if you are on the same group"], 1, 0.82, 0} }) end end, 5); end else ShowTooltip({ {1, "WeakAuras", 0.5, 0, 1}, {1, L["Malformed WeakAuras link"], 1, 0, 0} }); end end end); local compressedTablesCache = {} function TableToString(inTable, forChat) local serialized = LibSerialize:SerializeEx(configForLS, inTable) local compressed -- get from / add to cache if compressedTablesCache[serialized] then compressed = compressedTablesCache[serialized].compressed compressedTablesCache[serialized].lastAccess = time() else compressed = LibDeflate:CompressDeflate(serialized, configForDeflate) compressedTablesCache[serialized] = { compressed = compressed, lastAccess = time(), } end -- remove cache items after 5 minutes for k, v in pairs(compressedTablesCache) do if v.lastAccess < (time() - 300) then compressedTablesCache[k] = nil end end local encoded = "!WA:2!" if(forChat) then encoded = encoded .. LibDeflate:EncodeForPrint(compressed) else encoded = encoded .. LibDeflate:EncodeForWoWAddonChannel(compressed) end return encoded end function StringToTable(inString, fromChat) -- encoding format: -- version 0: simple b64 string, compressed with LC and serialized with AS -- version 1: b64 string prepended with "!", compressed with LD and serialized with AS -- version 2+: b64 string prepended with !WA:N! (where N is encode version) -- compressed with LD and serialized with LS local _, _, encodeVersion, encoded = inString:find("^(!WA:%d+!)(.+)$") if encodeVersion then encodeVersion = tonumber(encodeVersion:match("%d+")) else encoded, encodeVersion = inString:gsub("^%!", "") end local decoded if(fromChat) then if encodeVersion > 0 then decoded = LibDeflate:DecodeForPrint(encoded) else decoded = decodeB64(encoded) end else decoded = LibDeflate:DecodeForWoWAddonChannel(encoded) end if not decoded then return "Error decoding." end local decompressed, errorMsg = nil, "unknown compression method" if encodeVersion > 0 then decompressed = LibDeflate:DecompressDeflate(decoded) else decompressed, errorMsg = Compresser:Decompress(decoded) end if not(decompressed) then return "Error decompressing: " .. errorMsg end local success, deserialized if encodeVersion < 2 then success, deserialized = Serializer:Deserialize(decompressed) else success, deserialized = LibSerialize:Deserialize(decompressed) end if not(success) then return "Error deserializing "..deserialized end return deserialized end Private.StringToTable = StringToTable function Private.DisplayToString(id, forChat) local data = WeakAuras.GetData(id); if(data) then data.uid = data.uid or GenerateUniqueID() local transmitData = CompressDisplay(data); local children = data.controlledChildren; local transmit = { m = "d", d = transmitData, v = 1421, -- Version of Transmisson, won't change anymore. s = versionString }; local firstTrigger = data.triggers[1].trigger if(firstTrigger.type == "aura" and WeakAurasOptionsSaved and WeakAurasOptionsSaved.spellCache) then transmit.a = {}; for i,v in pairs(firstTrigger.names) do transmit.a[v] = WeakAuras.spellCache.GetIcon(v); end end if(children) then transmit.c = {}; local uids = {} for index,childID in pairs(children) do local childData = WeakAuras.GetData(childID); if(childData) then if childData.uid then if uids[childData.uid] then childData.uid = GenerateUniqueID() else uids[childData.uid] = true end else childData.uid = GenerateUniqueID() end transmit.c[index] = CompressDisplay(childData); end end end return TableToString(transmit, forChat); else return ""; end end local function recurseStringify(data, level, lines) for k, v in pairs(data) do local lineFormat = strrep(" ", level) .. "[%s] = %s" local form1, form2, value local kType, vType = type(k), type(v) if kType == "string" then form1 = "%q" elseif kType == "number" then form1 = "%d" else form1 = "%s" end if vType == "string" then form2 = "%q" v = v:gsub("\\", "\\\\"):gsub("\n", "\\n"):gsub("\"", "\\\"") elseif vType == "boolean" then v = tostring(v) form2 = "%s" else form2 = "%s" end lineFormat = lineFormat:format(form1, form2) if vType == "table" then tinsert(lines, lineFormat:format(k, "{")) recurseStringify(v, level + 1, lines) tinsert(lines, strrep(" ", level) .. "},") else tinsert(lines, lineFormat:format(k, v) .. ",") end end end function Private.DataToString(id) local data = WeakAuras.GetData(id) if data then return Private.SerializeTable(data):gsub("|", "||") end end function Private.SerializeTable(data) local lines = {"{"} recurseStringify(data, 1, lines) tinsert(lines, "}") return table.concat(lines, "\n") end function Private.RefreshTooltipButtons() importButton:Disable() importButton.tooltipText = nil showCodeButton:Enable() if not pendingData.codes or #pendingData.codes == 0 then showCodeButton:Hide() else showCodeButton:Show() end urlEditBox:Enable() if pendingData.url then urlEditBox:Show() urlEditBox.text = pendingData.url urlEditBox:SetText(pendingData.url) urlEditBox:HighlightText(0, 0) else urlEditBox:Hide() end if InCombatLockdown() then importButton:SetText(L["In Combat"]) importButton.tooltipText = L["Importing is disabled while in combat"] showCodeButton:Disable() elseif WeakAuras.IsImporting() then importButton:SetText(L["Import in progress"]) elseif pendingData.newData then if pendingData.activeCategories then if pendingData.mode == 1 then for button in pairs(pendingData.activeCategories) do button.text:SetTextColor(.5, .5, .5) end else for button in pairs(pendingData.activeCategories) do button.text:SetTextColor(1, 0.82, 0) end end end if pendingData.mode == #radioButtons and pendingData.buttonsChecked == 0 then importButton:SetText(L["Choose a category"]) else importButton:Enable() if pendingData.diffs then if pendingData.mode ~= 1 then importButton:SetText(L["Import as Update"]) else importButton:SetText(L["Import as Copy"]) end else if pendingData.mode == 1 then importButton:SetText(L["Import as Copy"]) elseif #pendingData.newData > 0 then importButton:SetText(L["Import Group"]) else importButton:SetText(L["Import"]) end end end end local importWidth = importButton.Text:GetStringWidth() importButton:SetWidth(importWidth + 30) end buttonAnchor:SetScript("OnEvent", Private.RefreshTooltipButtons) local function SetCheckButtonStates(radioButtonAnchor, activeCategories) if activeCategories then for i, button in ipairs(radioButtons) do button:Show() button:Enable() button:SetChecked(i == pendingData.mode) button:SetPoint("TOPLEFT", radioButtonAnchor, "TOPLEFT", 0, -20 * (i - .7)) end local checkButtonAnchor = radioButtons[#radioButtons] local buttonsChecked, buttonsShown = 0, 0 for i, button in ipairs(checkButtons) do if activeCategories[button] then button:Show() button:Enable() button:SetPoint("TOPLEFT", checkButtonAnchor, "TOPLEFT", 17, -27*(0.6 + buttonsShown)) button:SetChecked(button.default) buttonsShown = buttonsShown + 1 buttonsChecked = buttonsChecked + (button:GetChecked() and 1 or 0) else button:Hide() button:Disable() end end pendingData.buttonsChecked = buttonsChecked else for _, button in ipairs(radioButtons) do button:Hide() button:Disable() end for _, button in ipairs(checkButtons) do button:Disable() button:Hide() end end end function ShowTooltip(lines, linesFromTop, activeCategories) ItemRefTooltip:Show(); if not ItemRefTooltip:IsVisible() then ItemRefTooltip:SetOwner(UIParent, "ANCHOR_PRESERVE"); end ItemRefTooltip:ClearLines(); for i, line in ipairs(lines) do local sides, a1, a2, a3, a4, a5, a6, a7, a8 = unpack(line); if(sides == 1) then ItemRefTooltip:AddLine(a1, a2, a3, a4, a5); elseif(sides == 2) then ItemRefTooltip:AddDoubleLine(a1, a2, a3, a4, a5, a6, a7, a8); end end ItemRefTooltip:Show() if pendingData.newData then local checkButtonAnchor = _G["ItemRefTooltipTextLeft" .. (linesFromTop or 1)] SetCheckButtonStates(checkButtonAnchor, activeCategories) Private.RefreshTooltipButtons() buttonAnchor:Show() else buttonAnchor:Hide() end end local function notEmptyString(str) return str and str ~= "" and string.find(str, "%S") end local function addCode(codes, text, code, ...) -- The 4th paramter is a "check" if the code is active -- The following line let's distinguish between addCode(a, b, c, nil) and addCode(a, b, c) if (select("#", ...) > 0) then if not select(1, ...) then return end end if code and notEmptyString(code) then local t = {}; t.text = text; t.value = text t.code = code tinsert(codes, t); end end -- TODO: Should savedvariables data ever be refactored, then shunting the custom scripts -- into their own special subtable will allow us to simplify the scam check significantly. local function checkTrigger(codes, id, trigger, untrigger) if not trigger or trigger.type ~= "custom" then return end; addCode(codes, L["%s Trigger Function"]:format(id), trigger.custom) if trigger.custom_type == "stateupdate" then addCode(codes, L["%s Custom Variables"]:format(id), trigger.customVariables, trigger.custom_type == "stateupdate") else addCode(codes, L["%s Untrigger Function"]:format(id), untrigger and untrigger.custom) addCode(codes, L["%s Duration Function"]:format(id), trigger.customDuration) addCode(codes, L["%s Name Function"]:format(id), trigger.customName) addCode(codes, L["%s Icon Function"]:format(id), trigger.customIcon) addCode(codes, L["%s Texture Function"]:format(id),trigger.customTexture) addCode(codes, L["%s Stacks Function"]:format(id), trigger.customStacks) for i = 1, 7 do local property = "customOverlay" .. i; addCode(codes, L["%s Overlay Function"]:format(id), trigger[property]) end end end local function checkAnimation(codes, id, a) if not a or a.type ~= "custom" then return end addCode(codes, L["%s - Alpha Animation"]:format(id), a.alphaFunc, a.alphaType == "custom" and a.use_alpha) addCode(codes, L["%s - Translate Animation"]:format(id), a.translateFunc, a.translateType == "custom" and a.use_translate) addCode(codes, L["%s - Scale Animation"]:format(id), a.scaleFunc, a.scaleType == "custom" and a.use_scale) addCode(codes, L["%s - Rotate Animation"]:format(id), a.rotateFunc, a.rotateType == "custom" and a.use_rotate) addCode(codes, L["%s - Color Animation"]:format(id), a.colorFunc, a.colorType == "custom" and a.use_color) end local function scamCheck(codes, data) for i, v in ipairs(data.triggers) do checkTrigger(codes, L["%s - %i. Trigger"]:format(data.id, i), v.trigger, v.untrigger); end addCode(codes, L["%s - Trigger Logic"]:format(data.id), data.triggers.customTriggerLogic, data.triggers.disjunctive == "custom"); addCode(codes, L["%s - Custom Text"]:format(data.id), data.customText) addCode(codes, L["%s - Custom Anchor"]:format(data.id), data.customAnchor, data.anchorFrameType == "CUSTOM") if (data.actions) then if data.actions.init then addCode(codes, L["%s - Init Action"]:format(data.id), data.actions.init.custom, data.actions.init.do_custom) end if data.actions.start then addCode(codes, L["%s - Start Action"]:format(data.id), data.actions.start.custom, data.actions.start.do_custom) addCode(codes, L["%s - Start Custom Text"]:format(data.id), data.actions.start.message_custom, data.actions.start.do_message) end if data.actions.finish then addCode(codes, L["%s - Finish Action"]:format(data.id), data.actions.finish.custom, data.actions.finish.do_custom) addCode(codes, L["%s - Finish Custom Text"]:format(data.id), data.actions.finish.message_custom, data.actions.finish.do_message) end end if (data.animation) then checkAnimation(codes, L["%s - Start"]:format(data.id), data.animation.start); checkAnimation(codes, L["%s - Main"]:format(data.id), data.animation.main); checkAnimation(codes, L["%s - Finish"]:format(data.id), data.animation.finish); end addCode(codes, L["%s - Custom Grow"]:format(data.id), data.customGrow, data.regionType == "dynamicgroup" and data.grow == "CUSTOM") addCode(codes, L["%s - Custom Sort"]:format(data.id), data.customSort, data.regionType == "dynamicgroup" and data.sort == "custom") addCode(codes, L["%s - Custom Anchor"]:format(data.id), data.customAnchorPerUnit, data.regionType == "dynamicgroup" and data.grow ~= "CUSTOM" and data.useAnchorPerUnit and data.anchorPerUnit == "CUSTOM") if (data.conditions) then for _, condition in ipairs(data.conditions) do if (condition and condition.changes) then for _, property in ipairs(condition.changes) do if ((property.property == "chat" or property.property == "customcode") and type(property.value) == "table" and property.value.custom) then addCode(codes, L["%s - Condition Custom Chat"]:format(data.id), property.value.custom); end end end end end end local ignoredForDiffChecking = CreateFromMixins(Private.internal_fields, Private.non_transmissable_fields) local function recurseDiff(ours, theirs) local diff, seen, same = {}, {}, true for key, ourVal in pairs(ours) do if not ignoredForDiffChecking[key] then seen[key] = true local theirVal = theirs[key] if type(ourVal) == "table" and type(theirVal) == "table" then local diffVal = recurseDiff(ourVal, theirVal) if diffVal then diff[key] = diffVal same = false end elseif ourVal ~= theirVal and -- of course, floating points can be nonequal by less than we could possibly care not(type(ourVal) == "number" and type(theirVal) == "number" and math.abs(ourVal - theirVal) < 1e-6) then if (theirVal == nil) then diff[key] = deleted else diff[key] = theirVal; end same = false end end end for key, theirVal in pairs(theirs) do if not seen[key] and not ignoredForDiffChecking[key] then diff[key] = theirVal same = false end end if not same then return diff end end -- for debug purposes local function recurseSerial(lines, depth, chunk) for k, v in pairs(chunk) do if v == deleted then tinsert(lines, string.rep(" ", depth) .. "|cFFFF0000" .. k .. " -> deleted|r") elseif type(v) == "table" then tinsert(lines, string.rep(" ", depth) .. k .. " -> {") recurseSerial(lines, depth + 1, v) tinsert(lines, string.rep(" ", depth) .. "}") else tinsert(lines, string.rep(" ", depth) .. k .. " -> " .. tostring(v)) end end end local function diff(ours, theirs) -- generates a diff which WeakAuras.Update can use local debug = false if not ours or not theirs then return end local diff = recurseDiff(ours, theirs) if diff then if debug then local lines = { "==========================", "Diff detected: ", "ours: " .. (ours.id or "unknown") .. "," .. tostring(ours.uid), "theirs: " .. (theirs.id or "unknown") .. "," .. tostring(theirs.uid), "{", } recurseSerial(lines, 1, diff) tinsert(lines, "}") tinsert(lines, "==========================") print(table.concat(lines, "\n")) end return diff end end local function findMatch(data, children) local function isParentMatch(old, new) if old.parent then return end if old.uid and new.uid then if old.uid ~= new.uid then return else if (children == nil) ~= (old.controlledChildren == nil) then return end return true end else if children then if not old.controlledChildren then return end return old.id == new.id else if old.controlledChildren then return end return old.id == new.id end end end local oldParent = WeakAuras.GetData(data.id) if oldParent and not isParentMatch(oldParent, data) then oldParent = nil end if not oldParent or not isParentMatch(oldParent, data) then oldParent = nil if data.uid then for id, existingData in pairs(WeakAurasSaved.displays) do if isParentMatch(existingData, data) then oldParent = existingData break end end else return nil end end return oldParent end local function MatchInfo(data, children, oldParent) if oldParent then -- Either both have children, or both don't have if type(children) ~= type(oldParent.controlledChildren) then return nil end else -- match the parent/single aura (if no children) oldParent = findMatch(data, children) end if not oldParent then return nil end -- setup local info = { mode = 1, unmodified = 0, modified = 0, added = 0, deleted = 0, oldData = {}, newData = {}, indexMap = { oldToNew = {}, newToOld = {}, }, diffs = {}, activeCategories = {}, } info.oldData[0] = oldParent if info.oldData[0].preferToUpdate then info.mode = 2 end info.newData[0] = data info.diffs[0] = diff(oldParent, data) if info.diffs[0] then info.modified = info.modified + 1 end local hybridTables if oldParent.sortHybridTable or data.sortHybridTable then hybridTables = { old = {}, new = {}, } if oldParent.sortHybridTable then for id, isHybrid in pairs(oldParent.sortHybridTable) do hybridTables.old[id] = isHybrid end end if data.sortHybridTable then for id, isHybrid in pairs(data.sortHybridTable) do hybridTables.new[id] = isHybrid end end end if children then -- setup info.deleted = #oldParent.controlledChildren info.added = #children local UIDtoID, IDtoIndex = {}, {} for index, oldChildID in ipairs(oldParent.controlledChildren) do local oldChild = WeakAuras.GetData(oldChildID) info.oldData[index] = oldChild IDtoIndex[oldChildID] = index if oldChild.uid and not UIDtoID[oldChild.uid] then UIDtoID[oldChild.uid] = oldChildID end end -- confirm uid matches, find tentative matches local tentativeMatches = {} for newIndex, newChild in ipairs(children) do info.newData[newIndex] = newChild local oldIndex = IDtoIndex[newChild.id] local matchedID = newChild.uid and UIDtoID[newChild.uid] if matchedID then oldIndex = IDtoIndex[matchedID] info.indexMap.oldToNew[oldIndex] = newIndex info.indexMap.newToOld[newIndex] = oldIndex info.deleted = info.deleted - 1 info.added = info.added - 1 if hybridTables then if (hybridTables.new[newChild.id] and not hybridTables.old[matchedID]) or (not hybridTables.new[newChild.id] and hybridTables.old[matchedID]) then info.activeCategories[checkButtons.arrangement] = true hybridTables = nil else hybridTables.new[newChild.id] = nil hybridTables.old[matchedID] = nil end end UIDtoID[newChild.uid] = nil tentativeMatches[oldIndex] = nil elseif oldIndex then tentativeMatches[oldIndex] = newIndex end end -- confirm remaining tentative matches for oldIndex, newIndex in pairs(tentativeMatches) do local newChild, oldChild = info.newData[newIndex], info.oldData[oldIndex] if not newChild.uid or not oldChild.uid or newChild.uid == oldChild.uid then if hybridTables then if (hybridTables.new[newChild.id] and not hybridTables.old[oldChild.id]) or (not hybridTables.new[newChild.id] and hybridTables.old[oldChild.id]) then info.activeCategories[checkButtons.arrangement] = true hybridTables = nil else hybridTables.new[newChild.id] = nil hybridTables.old[oldChild.id] = nil end end info.indexMap.oldToNew[oldIndex] = newIndex info.indexMap.newToOld[newIndex] = oldIndex info.deleted = info.deleted - 1 info.added = info.added - 1 end end if hybridTables then if next(hybridTables.new) ~= nil or next(hybridTables.old) ~= nil then info.activeCategories[checkButtons.arrangement] = true end end -- detect order mismatch and find diffs local lastMatch = 0 for newIndex, newChild in ipairs(info.newData) do local oldIndex = info.indexMap.newToOld[newIndex] if oldIndex then local oldChild = info.oldData[oldIndex] if lastMatch and lastMatch > oldIndex then lastMatch = nil info.activeCategories[checkButtons.arrangement] = true else lastMatch = oldIndex end local childDiff = diff(oldChild, newChild, true) if childDiff then info.modified = info.modified + 1 info.diffs[newIndex] = childDiff end end end end if info.added ~= 0 then info.activeCategories[checkButtons.newchildren] = true end if info.deleted ~= 0 then info.activeCategories[checkButtons.oldchildren] = true end if info.modified ~= 0 then for i, diff in pairs(info.diffs) do for key in pairs(diff) do local button = keyToButton[key] if button then if i > 0 and button == checkButtons.anchor then button = checkButtons.arrangement end info.activeCategories[button] = true end end end end local modified = next(info.activeCategories) ~= nil return modified and info or false end local function ShowDisplayTooltip(data, children, matchInfo, icon, icons, import, compressed) -- since we have new data, wipe the old pending data wipe(pendingData) local regionType = data.regionType; local regionData = regionOptions[regionType or ""] local displayName = regionData and regionData.displayName or regionType or ""; local tooltip = { {2, data.id, " ", 0.5333, 0, 1}, {2, displayName, " ", 1, 0.82, 0}, {1, " ", 1, 1, 1} }; pendingData.codes = {}; local highestVersion = data.internalVersion or 1; local hasDescription = data.desc and data.desc ~= ""; local hasUrl = data.url and data.url ~= ""; pendingData.url = hasUrl and data.url local hasVersion = (data.semver and data.semver ~= "") or (data.version and data.version ~= ""); local tocversion = data.tocversion; if hasDescription or hasUrl or hasVersion then tinsert(tooltip, {1, " "}); end if hasUrl then tinsert(tooltip, {1, " "}); end if hasDescription then tinsert(tooltip, {1, "\""..data.desc.."\"", 1, 0.82, 0, 1}); end if hasVersion then tinsert(tooltip, {1, L["Version: "] .. (data.semver or data.version), 1, 0.82, 0, 1}); end -- WeakAuras.GetData needs to be replaced temporarily so that when the subsequent code constructs the thumbnail for -- the tooltip, it will look to the incoming transmission data for child data. This allows thumbnails of incoming -- groups to be constructed properly. local RegularGetData = WeakAuras.GetData if children then WeakAuras.GetData = function(id) if(children) then for index, childData in pairs(children) do if(childData.id == id) then return childData; end end end end end local linesFromTop if import then -- import description pendingData.newData = {[0] = data} if children then for index,child in ipairs(children) do pendingData.newData[index] = child end end --TODO: only scamCheck codes that have actually changed for i = 0, #pendingData.newData do scamCheck(pendingData.codes, pendingData.newData[i]) end if matchInfo ~= nil then -- we know of at least one match tinsert(tooltip, {1, " "}) if type(matchInfo) ~= "table" then -- there is no difference whatsoever tinsert(tooltip, {1, L["You already have this group/aura. Importing will create a duplicate."], 1, 0.82, 0,}) else -- load pendingData for k,v in pairs(matchInfo) do pendingData[k] = v end -- tally up changes if children and #children > 0 then tinsert(tooltip, {1, L["This is a modified version of your group, |cff9900FF%s.|r"]:format(pendingData.oldData[0].id), 1, 0.82, 0,}) tinsert(tooltip, {1, " "}) if matchInfo.added ~= 0 then tinsert(tooltip, {1, L[" • %d auras added"]:format(matchInfo.added), 1, 0.82, 0}) end if matchInfo.modified ~= 0 then tinsert(tooltip, {1, L[" • %d auras modified"]:format(matchInfo.modified), 1, 0.82, 0}) end if matchInfo.deleted ~= 0 then tinsert(tooltip, {1, L[" • %d auras deleted"]:format(matchInfo.deleted), 1, 0.82, 0}) end tinsert(tooltip, {1, " "}) tinsert(tooltip, {1, L["What do you want to do?"], 1, 0.82, 0}) tinsert(tooltip, {1, " "}) else local oldID = type(matchInfo.oldData[0]) == "table" and matchInfo.oldData[0].id or "unknown" tinsert(tooltip, {1, L["This is a modified version of your aura, |cff9900FF%s.|r"]:format(pendingData.oldData[0].id), 1, 0.82, 0}) tinsert(tooltip, {1, L["What do you want to do?"], 1, 0.82, 0}) tinsert(tooltip, {1, " "}) end linesFromTop = #tooltip for _ in ipairs(radioButtons) do tinsert(tooltip, {1, " "}) end for _, button in ipairs(checkButtons) do if pendingData.activeCategories[button] then tinsert(tooltip, {1, " "}) tinsert(tooltip, {1, " "}) end end end else if not children and (not data.controlledChildren or #data.controlledChildren == 0) then tinsert(tooltip, {1, L["No Children"], 1, 1, 1}) for triggernum = 1, min(#data.triggers, 10) do local trigger = data.triggers[triggernum].trigger if(trigger) then if(trigger.type == "aura") then for index, name in pairs(trigger.names) do local left = " "; if(index == 1) then if(#trigger.names > 0) then if(#trigger.names > 1) then left = L["Auras:"]; else left = L["Aura:"]; end end end if (icons) then tinsert(tooltip, {2, left, name..(icons[name] and (" |T"..icons[name]..":12:12:0:0:64:64:4:60:4:60|t") or ""), 1, 1, 1, 1, 1, 1}); else local icon = WeakAuras.spellCache and WeakAuras.spellCache.GetIcon(name) or "Interface\\Icons\\INV_Misc_QuestionMark"; tinsert(tooltip, {2, left, name.." |T"..icon..":12:12:0:0:64:64:4:60:4:60|t", 1, 1, 1, 1, 1, 1}); end end elseif(trigger.type == "aura2") then tinsert(tooltip, {2, L["Trigger:"], L["Aura"], 1, 1, 1, 1, 1, 1}); elseif(Private.category_event_prototype[trigger.type]) then tinsert(tooltip, {2, L["Trigger:"], (Private.event_prototypes[trigger.event].name or L["Undefined"]), 1, 1, 1, 1, 1, 1}); if(trigger.event == "Combat Log" and trigger.subeventPrefix and trigger.subeventSuffix) then tinsert(tooltip, {2, L["Message type:"], (Private.subevent_prefix_types[trigger.subeventPrefix] or L["Undefined"]).." "..(Private.subevent_suffix_types[trigger.subeventSuffix] or L["Undefined"]), 1, 1, 1, 1, 1, 1}); end else tinsert(tooltip, {2, L["Trigger:"], L["Custom"], 1, 1, 1, 1, 1, 1}); end end end else tinsert(tooltip, {1, L["Children:"], 1, 1, 1}) local excessChildren = -30 for _, child in pairs(children or data.controlledChildren) do excessChildren = excessChildren + 1 if children then highestVersion = max(highestVersion, child.internalVersion or 1) end if excessChildren <= 0 then tinsert(tooltip, {2, " ", child.id, 1, 1, 1, 1, 1, 1}) end tocversion = tocversion or child.tocversion end if excessChildren > 0 then tinsert(tooltip, {2, " ", "[...]", 1, 1, 1, 1, 1, 1}) tinsert(tooltip, {1, L["%s total auras"]:format(excessChildren + 30), "", 1, 1, 1, 1}) end end end tinsert(tooltip, {1, " "}) if(type(import) == "string" and import ~= "unknown") then tinsert(tooltip, {2, L["From"]..": "..import, " ", 0, 1, 0}) end if #pendingData.codes > 0 then tinsert(tooltip, {1, L["This aura contains custom Lua code."], 1, 0, 0}) tinsert(tooltip, {1, L["Make sure you can trust the person who sent it!"], 1, 0, 0}) end if (highestVersion > WeakAuras.InternalVersion()) then tinsert(tooltip, {1, L["This aura was created with a newer version of WeakAuras."], 1, 0, 0}) tinsert(tooltip, {1, L["It might not work correctly with your version!"], 1, 0, 0}) end if WeakAuras.IsClassic() and (not tocversion or tocversion > 20000) then tinsert(tooltip, {1, L["This aura was created with the retail version of World of Warcraft."], 1, 0, 0}) tinsert(tooltip, {1, L["It might not work correctly on Classic!"], 1, 0, 0}) elseif tocversion and not WeakAuras.IsClassic() and tocversion < 20000 then tinsert(tooltip, {1, L["This aura was created with the Classic version of World of Warcraft."], 1, 0, 0}) tinsert(tooltip, {1, L["It might not work correctly on Retail!"], 1, 0, 0}) end tinsert(tooltip, {2, " ", " ", 0, 1, 0}) tinsert(tooltip, {2, " ", " ", 0, 1, 0}) end if not IsAddOnLoaded('WeakAurasOptions') then LoadAddOn('WeakAurasOptions') end if thumbnailAnchor.currentThumbnail then regionOptions[thumbnailAnchor.currentThumbnailType].releaseThumbnail(thumbnailAnchor.currentThumbnail) thumbnailAnchor.currentThumbnail = nil end if regionOptions[regionType] then local ok, thumbnail = pcall(regionOptions[regionType].acquireThumbnail, thumbnailAnchor, data); if not ok then error(string.format("Error creating thumbnail for %s %s", regionType, thumbnail), 2) end thumbnailAnchor.currentThumbnail = thumbnail thumbnailAnchor.currentThumbnailType = regionType thumbnail:SetAllPoints(thumbnailAnchor); thumbnail:SetParent(thumbnailAnchor) if (thumbnail.SetIcon) then local i; if(icon) then i = icon; end if (i) then thumbnail:SetIcon(i); else thumbnail:SetIcon(); end end end WeakAuras.GetData = RegularGetData or WeakAuras.GetData ShowTooltip(tooltip, linesFromTop, matchInfo and matchInfo.activeCategories) end function WeakAuras.Import(inData, target) local data, children, icon, icons if type(inData) == 'string' then -- encoded data local received = StringToTable(inData, true) if type(received) == 'string' then -- this is probably an error message from LibDeflate. Display it. ShowTooltip{ {1, "WeakAuras", 0.5333, 0, 1}, {1, received, 1, 0, 0, 1} } return nil, received elseif received.m == "d" then data = received.d children = received.c icon = received.i icons = received.a end elseif type(inData.d) == 'table' then data = inData.d children = inData.c icon = inData.i icons = inData.a end if type(data) ~= "table" then return nil, "Invalid import data." end local status, msg = true, "" if type(target) ~= 'nil' then local targetData local uid = type(target) == 'table' and target.uid or target if type(uid) == 'string' then for _, installedData in pairs(WeakAurasSaved.displays) do if installedData.uid == uid then targetData = installedData break end end end if not targetData then status = false msg = "Invalid update target. Importing as an unknown payload." target = nil else target = targetData end end WeakAuras.PreAdd(data) if children then for _, child in ipairs(children) do WeakAuras.PreAdd(child) end end tooltipLoading = nil; local matchInfo = MatchInfo(data, children, target) if matchInfo == nil and target then return false, "Import data did not match to target" end ShowDisplayTooltip(data, children, matchInfo, icon, icons, "unknown") return status, msg end -- backwards compatibility WeakAuras.ImportString = WeakAuras.Import local function crossRealmSendCommMessage(prefix, text, target, queueName, callbackFn, callbackArg) local chattype = "WHISPER" if target and not UnitIsSameServer(target) then if UnitInRaid(target) then chattype = "RAID" text = ("§§%s:%s"):format(target, text) elseif UnitInParty(target) then chattype = "PARTY" text = ("§§%s:%s"):format(target, text) end end Comm:SendCommMessage(prefix, text, chattype, target, queueName, callbackFn, callbackArg) end local safeSenders = {} function RequestDisplay(characterName, displayName) safeSenders[characterName] = true safeSenders[Ambiguate(characterName, "none")] = true local transmit = { m = "dR", d = displayName }; local transmitString = TableToString(transmit); crossRealmSendCommMessage("WeakAuras", transmitString, characterName); end function TransmitError(errorMsg, characterName) local transmit = { m = "dE", eM = errorMsg }; crossRealmSendCommMessage("WeakAuras", TableToString(transmit), characterName); end function TransmitDisplay(id, characterName) local encoded = Private.DisplayToString(id); if(encoded ~= "") then crossRealmSendCommMessage("WeakAuras", encoded, characterName, "BULK", function(displayName, done, total) crossRealmSendCommMessage("WeakAurasProg", done.." "..total.." "..displayName, characterName, "ALERT"); end, id); else TransmitError("dne", characterName); end end Comm:RegisterComm("WeakAurasProg", function(prefix, message, distribution, sender) if distribution == "PARTY" or distribution == "RAID" then local dest, msg = string.match(message, "^§§(.+):(.+)$") if dest then local dName, dServer = string.match(dest, "^(.*)-(.*)$") local myName, myServer = UnitFullName("player") if myName == dName and myServer == dServer then message = msg else return end end end if tooltipLoading and ItemRefTooltip:IsVisible() and safeSenders[sender] then receivedData = true; local done, total, displayName = strsplit(" ", message, 3) done = tonumber(done) total = tonumber(total) if(done and total and total >= done) then local red = min(255, (1 - done / total) * 511) local green = min(255, (done / total) * 511) ShowTooltip({ {2, "WeakAuras", displayName, 0.5, 0, 1, 1, 1, 1}, {1, L["Receiving display information"]:format(sender), 1, 0.82, 0}, {2, " ", ("|cFF%2x%2x00"):format(red, green)..done.."|cFF00FF00/"..total} }) end end end) Comm:RegisterComm("WeakAuras", function(prefix, message, distribution, sender) if distribution == "PARTY" or distribution == "RAID" then local dest, msg = string.match(message, "^§§([^:]+):(.+)$") if dest then local dName, dServer = string.match(dest, "^(.*)-(.*)$") local myName, myServer = UnitFullName("player") if myName == dName and myServer == dServer then message = msg else return end end end local received = StringToTable(message); if(received and type(received) == "table" and received.m) then if(received.m == "d") and safeSenders[sender] then tooltipLoading = nil; local data, children, icon, icons = received.d, received.c, received.i, received.a WeakAuras.PreAdd(data) if children then for _, child in ipairs(children) do WeakAuras.PreAdd(child) end end local matchInfo = MatchInfo(data, children) ShowDisplayTooltip(data, children, matchInfo, icon, icons, sender, true) elseif(received.m == "dR") then if(Private.linked and Private.linked[received.d]) then TransmitDisplay(received.d, sender); end elseif(received.m == "dE") then tooltipLoading = nil; if(received.eM == "dne") then ShowTooltip({ {1, "WeakAuras", 0.5333, 0, 1}, {1, L["Requested display does not exist"], 1, 0, 0} }); elseif(received.eM == "na") then ShowTooltip({ {1, "WeakAuras", 0.5333, 0, 1}, {1, L["Requested display not authorized"], 1, 0, 0} }); end end elseif(ItemRefTooltip.WeakAuras_Tooltip_Thumbnail and ItemRefTooltip.WeakAuras_Tooltip_Thumbnail:IsVisible()) then ShowTooltip({ {1, "WeakAuras", 0.5333, 0, 1}, {1, L["Transmission error"], 1, 0, 0} }); end end);