local addonName, addon = ... ----------------------------------------------------------------------- -- Make sure we are prepared -- local function print(...) _G.print("|cff259054BugSack:|r", ...) end if not LibStub then print("BugSack requires LibStub.") return end local L = addon.L local BugGrabber = BugGrabber if not BugGrabber then local msg = L["|cffff4411BugSack requires the |r|cff44ff44!BugGrabber|r|cffff4411 addon, which you can download from the same place you got BugSack. Happy bug hunting!|r"] local f = CreateFrame("Frame") f:SetScript("OnEvent", function() RaidNotice_AddMessage(RaidWarningFrame, msg, {r=1, g=0.3, b=0.1}) print(msg) f:UnregisterEvent("PLAYER_ENTERING_WORLD") f:SetScript("OnEvent", nil) f = nil end) f:RegisterEvent("PLAYER_ENTERING_WORLD") return end -- We seem fine, let the world access us. _G[addonName] = addon addon.healthCheck = true -- Sound local media = LibStub("LibSharedMedia-3.0") media:Register("sound", "BugSack: Fatality", "Interface\\AddOns\\"..addonName.."\\Media\\error.ogg") ----------------------------------------------------------------------- -- Utility -- local onError do local lastError = nil function onError() if not lastError or GetTime() > (lastError + 2) then if not addon.db.mute then local sound = media:Fetch("sound", addon.db.soundMedia) if addon.db.useMaster then PlaySoundFile(sound, "Master") else PlaySoundFile(sound) end end if addon.db.chatframe then print(L["There's a bug in your soup!"]) end lastError = GetTime() end -- If the frame is shown, we need to update it. if (addon.db.auto and not InCombatLockdown()) or (BugSackFrame and BugSackFrame:IsShown()) then addon:OpenSack() end addon:UpdateDisplay() end end ----------------------------------------------------------------------- -- Event handling -- do local eventFrame = CreateFrame("Frame", nil, InterfaceOptionsFramePanelContainer) eventFrame:SetScript("OnEvent", function(self, event, loadedAddon) if loadedAddon ~= addonName then return end self:UnregisterEvent("ADDON_LOADED") local ac = LibStub("AceComm-3.0", true) if ac then ac:Embed(addon) end local as = LibStub("AceSerializer-3.0", true) if as then as:Embed(addon) end local popup = _G.StaticPopupDialogs if type(popup) ~= "table" then popup = {} end if type(popup.BugSackSendBugs) ~= "table" then popup.BugSackSendBugs = { text = L["Send all bugs from the currently viewed session (%d) in the sack to the player specified below."], button1 = L["Send"], button2 = CLOSE, timeout = 0, whileDead = true, hideOnEscape = true, hasEditBox = true, OnAccept = function(self, data) local recipient = self.editBox:GetText() addon:SendBugsToUser(recipient, data) end, OnShow = function(self) self.button1:Disable() end, EditBoxOnTextChanged = function(self) local t = self:GetText() if t:len() > 2 and not t:find("%s") then self:GetParent().button1:Enable() else self:GetParent().button1:Disable() end end, enterClicksFirstButton = true, --OnCancel = function() show() end, -- Need to wrap it so we don't pass |self| as an error argument to show(). preferredIndex = STATICPOPUP_NUMDIALOGS, } end if type(BugSackDB) ~= "table" then BugSackDB = {} end local sv = BugSackDB sv.profileKeys = nil sv.profiles = nil if type(sv.mute) ~= "boolean" then sv.mute = false end if type(sv.auto) ~= "boolean" then sv.auto = false end if type(sv.chatframe) ~= "boolean" then sv.chatframe = false end if type(sv.soundMedia) ~= "string" then sv.soundMedia = "BugSack: Fatality" end if type(sv.fontSize) ~= "string" then sv.fontSize = "GameFontHighlight" end if type(sv.altwipe) ~= "boolean" then sv.altwipe = false end if type(sv.useMaster) ~= "boolean" then sv.useMaster = false end addon.db = sv -- Make sure we grab any errors fired before bugsack loaded. local session = addon:GetErrors(BugGrabber:GetSessionId()) if #session > 0 then onError() end if addon.RegisterComm then addon:RegisterComm("BugSack", "OnBugComm") end -- Set up our error event handler BugGrabber.RegisterCallback(addon, "BugGrabber_BugGrabbed", onError) SlashCmdList.BugSack = function(msg) msg = msg:lower() if msg == "show" then addon:OpenSack() else if InterfaceOptionsFrame_OpenToCategory then InterfaceOptionsFrame_OpenToCategory(addonName) InterfaceOptionsFrame_OpenToCategory(addonName) else Settings.OpenToCategory(addon.settingsCategory.ID) end end end SLASH_BugSack1 = "/bugsack" self:SetScript("OnEvent", nil) end) eventFrame:RegisterEvent("ADDON_LOADED") addon.frame = eventFrame end ----------------------------------------------------------------------- -- API -- function addon:UpdateDisplay() -- noop, hooked by displays end do local errors = {} function addon:GetErrors(sessionId) -- XXX I've never liked this function, maybe a BugGrabber redesign is in order, -- XXX where we have one subtable in the DB per session ID. if sessionId then wipe(errors) local db = BugGrabber:GetDB() for i, e in next, db do if sessionId == e.session then errors[#errors + 1] = e end end return errors else return BugGrabber:GetDB() end end end do local function colorStack(ret) ret = tostring(ret) or "" -- Yes, it gets called with nonstring from somewhere /mikk ret = ret:gsub("[%.I][%.n][%.t][%.e][%.r]face/", "") ret = ret:gsub("%.?%.?%.?/?AddOns/", "") ret = ret:gsub("|([^chHr])", "||%1"):gsub("|$", "||") -- Pipes ret = ret:gsub("<(.-)>", "|cffffea00<%1>|r") -- Things wrapped in <> ret = ret:gsub("%[(.-)%]", "|cffffea00[%1]|r") -- Things wrapped in [] ret = ret:gsub("([\"`'])(.-)([\"`'])", "|cff8888ff%1%2%3|r") -- Quotes ret = ret:gsub(":(%d+)([%S\n])", ":|cff00ff00%1|r%2") -- Line numbers ret = ret:gsub("([^/]+%.lua)", "|cffffffff%1|r") -- Lua files return ret end addon.ColorStack = colorStack local function colorLocals(ret) ret = tostring(ret) or "" -- Yes, it gets called with nonstring from somewhere /mikk ret = ret:gsub("[%.I][%.n][%.t][%.e][%.r]face/", "") ret = ret:gsub("%.?%.?%.?/?AddOns/", "") ret = ret:gsub("|(%a)", "||%1"):gsub("|$", "||") -- Pipes ret = ret:gsub("> %@(.-):(%d+)", "> @|cffeda55f%1|r:|cff00ff00%2|r") -- Files/Line Numbers of locals ret = ret:gsub("(%s-)([%a_%(][%a_%d%*%)]+) = ", "%1|cffffff80%2|r = ") -- Table keys ret = ret:gsub("= (%-?[%d%p]+)\n", "= |cffff7fff%1|r\n") -- locals: number ret = ret:gsub("= nil\n", "= |cffff7f7fnil|r\n") -- locals: nil ret = ret:gsub("= true\n", "= |cffff9100true|r\n") -- locals: true ret = ret:gsub("= false\n", "= |cffff9100false|r\n") -- locals: false ret = ret:gsub("= <(.-)>", "= |cffffea00<%1>|r") -- Things wrapped in <> return ret end addon.ColorLocals = colorLocals local errorFormat = "%dx %s" local errorFormatLocals = "%dx %s\n\nLocals:\n%s" function addon:FormatError(err) if not err.locals then local s = colorStack(tostring(err.message) .. (err.stack and "\n"..tostring(err.stack) or "")) local l = colorLocals(tostring(err.locals)) return errorFormat:format(err.counter or -1, s, l) else local s = colorStack(tostring(err.message) .. (err.stack and "\n"..tostring(err.stack) or "")) local l = colorLocals(tostring(err.locals)) return errorFormatLocals:format(err.counter or -1, s, l) end end end function addon:Reset() BugGrabber:Reset() self:UpdateDisplay() print(L["All stored bugs have been exterminated painfully."]) end -- Sends the current session errors to another player using AceComm-3.0 function addon:SendBugsToUser(player, session) if type(player) ~= "string" or player:trim():len() < 2 then error(L["Player needs to be a valid name."]) end if not self.Serialize then return end local errors = self:GetErrors(session) if not errors or #errors == 0 then return end local sz = self:Serialize(errors) self:SendCommMessage("BugSack", sz, "WHISPER", player, "BULK") print(L["%d bugs have been sent to %s. He must have BugSack to be able to examine them."]:format(#errors, player)) end function addon:OnBugComm(prefix, message, _, sender) if prefix ~= "BugSack" or not self.Deserialize then return end local good, deSz = self:Deserialize(message) if not good then print(L["Failure to deserialize incoming data from %s."]:format(sender)) return end -- Store recieved errors in the current session database with a source set to the sender local s = BugGrabber:GetSessionId() for i, err in next, deSz do err.source = sender err.session = s BugGrabber:StoreError(err) end print(L["You've received %d bugs from %s."]:format(#deSz, sender)) wipe(deSz) deSz = nil end --[[ do local commFormat = "1#%s#%s" local function transmit(command, target, argument) SendAddonMessage("BugGrabber", commFormat:format(command, argument), "WHISPER", target) end local retrievedErrors = {} function addon:GetErrorByPlayerAndID(player, id) if player == playerName then return self:GetErrorByID(id) end -- This error was linked by someone else, we need to retrieve it from them -- using the addon communication channel. if retrievedErrors[id] then return retrievedErrors[id] end transmit("FETCH", player, id) print(L.ERROR_INCOMING:format(id, player)) end local fakeAddon, comm, serializer = nil, nil, nil local function commBugCatcher(prefix, message, distribution, sender) local good, deSz = fakeAddon:Deserialize(message) if not good then print("damnit") return end retrievedErrors[deSz.originalId] = deSz end local function hasTransmitFacilities() if fakeAddon then return true end if not serializer then serializer = LibStub("AceSerializer-3.0", true) end if not comm then comm = LibStub("AceComm-3.0", true) end if comm and serializer then fakeAddon = {} comm:Embed(fakeAddon) serializer:Embed(fakeAddon) fakeAddon:RegisterComm("BGBug", commBugCatcher) return true end end function frame:CHAT_MSG_ADDON(event, prefix, message, distribution, sender) if prefix ~= "BugGrabber" then return end local version, command, argument = strsplit("#", message) if tonumber(version) ~= 1 or not command then return end if command == "FETCH" then local errorObject = addon:GetErrorByID(argument) if errorObject then if hasTransmitFacilities() then errorObject.originalId = argument local sz = fakeAddon:Serialize(errorObject) fakeAddon:SendCommMessage("BGBug", sz, "WHISPER", sender, "BULK") else -- We can only transmit a gimped and sanitized message transmit("BUG", sender, errorObject.message:sub(1, 240):gsub("#", "")) end else transmit("FAIL", sender, argument) end elseif command == "FAIL" then print(L.ERROR_FAILED_FETCH:format(argument, sender)) elseif command == "BUG" then print(L.CRIPPLED_ERROR:format(sender, argument)) end end end]]