-- File: WhisperEngine.lua -- Author: John Langone (Pazza - Bronzebeard) -- Description: This module handles whisper behaviors as well as their respective window actions. --[[ Extends Modules by adding: Module:PostEvent_Whisper(args[...]) Module:PostEvent_WhisperInform(args[...]) ]] -- imports local WIM = WIM; local _G = _G; local CreateFrame = CreateFrame; local hooksecurefunc = hooksecurefunc; local table = table; local pairs = pairs; local strupper = strupper; local gsub = gsub; local strlen = strlen; local strsub = strsub; local string = string; local IsShiftKeyDown = IsShiftKeyDown; local select = select; local unpack = unpack; local math = math; local time = time; local playerRealm = GetRealmName(); local GetPlayerInfoByGUID = GetPlayerInfoByGUID; local FlashClientIcon = FlashClientIcon; local ChatFrameUtil = ChatFrameUtil; -- set name space setfenv(1, WIM); -- create WIM Module local WhisperEngine = CreateModule("WhisperEngine", true); -- declare default settings for whispers. -- if new global env wasn't set to WIM's namespace, then your module would call as follows: -- WhisperEngine.db_defaults... or WIM.db_defaults... db_defaults.pop_rules.whisper = { --pop-up rule sets based off of your location resting = { onSend = true, onReceive = true, supress = true, autofocus = true, keepfocus = true, }, combat = { onSend = false, onReceive = false, supress = false, autofocus = false, keepfocus = false, }, pvp = { onSend = true, onReceive = true, supress = true, autofocus = false, keepfocus = false, }, arena = { onSend = false, onReceive = false, supress = false, autofocus = false, keepfocus = false, }, party = { onSend = true, onReceive = true, supress = true, autofocus = false, keepfocus = false, }, raid = { onSend = true, onReceive = true, supress = true, autofocus = false, keepfocus = false, }, other = { onSend = true, onReceive = true, supress = true, autofocus = false, keepfocus = false, }, alwaysOther = false, intercept = true, obeyAutoFocusRules = false, } db_defaults.displayColors.wispIn = { r=0.5607843137254902, g=0.03137254901960784, b=0.7607843137254902 } db_defaults.displayColors.wispOut = { r=1, g=0.07843137254901961, b=0.9882352941176471 } db_defaults.displayColors.BNwispIn = { r=0, g=0.4862745098039216, b=0.6549019607843137, } db_defaults.displayColors.BNwispOut = { r=0.1725490196078431, g=0.6352941176470588, b=1, } local Windows = windows.active.whisper; local WhisperQueue_Bowl = {}; -- used to recycle tables for queue local WhisperQueue = {}; -- active event queue local WhisperQueue_Index = {}; -- a quick reference to an active index local CF_MessageEventHandler_orig; -- used for a hook of the chat frame. Messaage filter handlers aren't sufficient. local addToTableUnique = addToTableUnique; local removeFromTable = removeFromTable; local recentSent = {}; local maxRecent = 10; local alertPushed = false; local function updateMinimapAlerts() local count = 0; for _, win in pairs(Windows) do if(not win:IsVisible()) then count = count + (win.unreadCount or 0); end end if(count == 0 and alertPushed) then alertPushed = false; MinimapPopAlert(L["Whispers"]); elseif(count > 0) then alertPushed = true; local color = db.displayColors.wispIn; MinimapPushAlert(L["Whispers"], RGBPercentToHex(color.r, color.g, color.b), count); -- DisplayTutorial(L["Whisper Received!"], L["You received a whisper which was hidden due to your current activity. You can change how whispers behave in WIM's options by typing"].." |cff69ccf0/wim|r"); end end local CHAT_EVENTS = { "CHAT_MSG_WHISPER", "CHAT_MSG_WHISPER_INFORM", "CHAT_MSG_AFK", "CHAT_MSG_DND", "CHAT_MSG_SYSTEM", "CHAT_MSG_BN_WHISPER", "CHAT_MSG_BN_WHISPER_INFORM", "CHAT_MSG_BN_INLINE_TOAST_ALERT" }; function WhisperEngine:OnEnableWIM() for i = 1, #CHAT_EVENTS do WhisperEngine:RegisterEvent(CHAT_EVENTS[i]); end end function WhisperEngine:OnEnable () for i = 1, #CHAT_EVENTS do if ChatFrameUtil and ChatFrameUtil.AddMessageEventFilter then ChatFrameUtil.AddMessageEventFilter(CHAT_EVENTS[i], WhisperEngine.ChatMessageEventFilter); else _G.ChatFrame_AddMessageEventFilter(CHAT_EVENTS[i], WhisperEngine.ChatMessageEventFilter); end end -- check if whisperMode is set to inline, if not, display a warning in the options. if _G.GetCVar and _G.GetCVar("whisperMode") ~= "inline" and db.whisperModeChecked ~= true then _G.StaticPopupDialogs["WIM_WHISPER_MODE"] = { preferredIndex = _G.STATICPOPUP_NUMDIALOGS, text = "WIM: "..L["It is recommended for whispers to be set to in-line in order to handle their behavior properly."], button1 = L["Set whispers to In-line"], button2 = _G.LATER, OnAccept = function() _G.SetCVar("whisperMode", "inline"); end, timeout = 0, whileDead = 1, hideOnEscape = 1 }; _G.StaticPopup_Show ("WIM_WHISPER_MODE"); db.whisperModeChecked = true; end end function WhisperEngine:OnDisable() for i = 1, #CHAT_EVENTS do if ChatFrameUtil and ChatFrameUtil.RemoveMessageEventFilter then ChatFrameUtil.RemoveMessageEventFilter(CHAT_EVENTS[i], WhisperEngine.ChatMessageEventFilter); else _G.ChatFrame_RemoveMessageEventFilter(CHAT_EVENTS[i], WhisperEngine.ChatMessageEventFilter); end end end local function safeName(user) -- nil check. For some reason, events get modified by other addons and return nil for user. if _G.type(user) ~= "string" or user == "" then return "" end -- check if cross realm or if realm is included and the same as player, then strip realm if string.find(user or "", "-") then local player, realm = user:match("^(.-)-(.-)$"); if string.lower(realm) == string.lower(env.realm) then user = player; end end return string.lower(user or ""); end local function findWhisperWindowByBNetID(bnID) for _, win in pairs(Windows) do if(win.isBN and win.bn.id == bnID) then return win; end end return nil; end local function getWhisperWindowByUser(user, isBN, bnID, fromEvent) if isBN then local _; local bnWin = findWhisperWindowByBNetID(bnID); local _user = user; _, user = GetBNGetFriendInfoByID(bnID) -- fix window handler when using the chat hyperlink user = user and user ~= "" and user or _user; -- if window already exists for bnID but name has changed, update it. if (bnWin and safeName(bnWin.theUser) ~= safeName(user)) then -- swap the indexed name to the new name Windows[safeName(user)] = bnWin; Windows[safeName(bnWin.theUser)] = nil; bnWin:Rename(user); return bnWin; end else user = string.gsub(user," ","") -- Drii: WoW build15050 whisper bug for x-realm server with space user = fromEvent and user or FormatUserName(user); end if(not user or user == "") then -- if invalid user, then return nil; return nil; end local obj = Windows[safeName(user)]; if(obj and obj.type == "whisper") then -- if the whisper window exists, return the object -- update name if from event obj.user = user obj.theUser = user return obj; else -- otherwise, create a new one. Windows[safeName(user)] = CreateWhisperWindow(user, function(win) win.isBN = isBN; win.bn = win.bn or {}; if(db.whoLookups or lists.gm[safeName(user)] or win.isBN) then win:SendWho(); -- send who request end win.online = true; end); return Windows[safeName(user)], true; end end local function windowDestroyed(self) if(IsShiftKeyDown() or self.forceShift) then local user = self:GetParent().theUser; Windows[safeName(user)].online = nil; Windows[safeName(user)].msgSent = nil; for k in pairs(Windows[safeName(user)].bn) do Windows[safeName(user)].bn[k] = nil; end Windows[safeName(user)].isBN = nil; Windows[safeName(user)] = nil; end end function WhisperEngine:OnWindowDestroyed(win) if(win.type == "whisper") then local user = win.theUser; Windows[safeName(user)] = nil; end end function WhisperEngine:OnWindowShow(win) updateMinimapAlerts(); end local splitMessage, splitMessageLinks = {}, {}; function SendSplitMessage(PRIORITY, HEADER, theMsg, CHANNEL, EXTRA, to) -- ignore completely empty messages if _G.type(theMsg) ~= "string" or theMsg == "" then return end -- for whisper-style channels we *must* have a target if (CHANNEL == "WHISPER" or CHANNEL == "BN_WHISPER") and (_G.type(to) ~= "string" or to == "") then -- no valid target, don't try to send or open windows return end -- determine isBNET local isBN, messageLimit = false, 255; if(Windows[safeName(to)] and Windows[safeName(to)].isBN) then isBN = true; messageLimit = 800; end -- seperate escape sequences when chained without spaces theMsg = string.gsub(theMsg, "|r|c", "|r |c"); theMsg = string.gsub(theMsg, "|t|T", "|t |T"); theMsg = string.gsub(theMsg, "|h|H", "|h |H"); -- parse out links as to not split them incorrectly. theMsg, results = string.gsub(theMsg, "(|H[^|]+|h.-|h|r)", function(theLink) table.insert(splitMessageLinks, theLink); return "\001\002"..paddString(#splitMessageLinks, "0", string.len(theLink)-4).."\003\004"; end); -- split up each word. SplitToTable(theMsg, "%s", splitMessage); --reconstruct message into chunks of no more than 255 characters. local chunk = ""; for i=1, #splitMessage + 1 do if(splitMessage[i] and string.len(chunk) + string.len(splitMessage[i]) < messageLimit) then chunk = chunk..splitMessage[i].." "; else -- reinsert links of necessary chunk = string.gsub(chunk, "\001\002%d+\003\004", function(link) local index = _G.tonumber(string.match(link, "(%d+)")); return splitMessageLinks[index] or link; end); if(isBN) then (_G.C_BattleNet and _G.C_BattleNet.SendWhisper or _G.BNSendWhisper)(Windows[safeName(to)].bn.id, chunk); else (_G.C_ChatInfo and _G.C_ChatInfo.SendChatMessage or _G.SendChatMessage)(chunk, CHANNEL, EXTRA, to) end chunk = (splitMessage[i] or "").." "; end end -- clean up for k, _ in pairs(splitMessage) do splitMessage[k] = nil; end for k, _ in pairs(splitMessageLinks) do splitMessageLinks[k] = nil; end end RegisterWidgetTrigger("msg_box", "whisper", "OnEnterPressed", function(self) local obj = self:GetParent(); local msg = PreSendFilterText(self:GetText()); -- do not send if in chat messaging lockdown (12.0.0+) if InChatMessagingLockdown() then return; end if(msg ~= "") then Windows[safeName(obj.theUser)].msgSent = true; SendSplitMessage("ALERT", "WIM", msg, "WHISPER", nil, obj.theUser); end self:SetText(""); end); -------------------------------------- -- Event Handlers -- -------------------------------------- local CMS_PATTERNS = { PLAYER_NOT_FOUND = _G.ERR_CHAT_PLAYER_NOT_FOUND_S:gsub("%%s", "(.+)"), CHAT_IGNORED = _G.CHAT_IGNORED:gsub("%%s", "(.+)"), FRIEND_ONLINE = _G.ERR_FRIEND_ONLINE_SS:gsub("%[", "%%["):gsub("%]", "%%]"):gsub("%%s", "(.+)"), FRIEND_OFFLINE = _G.ERR_FRIEND_OFFLINE_S:gsub("%%s", "(.+)") }; function WhisperEngine.ChatMessageEventFilter (frame, event, ...) -- Process all events except for CHAT_MSG_SYSTEM if (event ~= "CHAT_MSG_SYSTEM") then local ignore, block = (IgnoreOrBlockEvent or function () end)(event, ...) if (not frame._isWIM and not ignore and not block) then -- execute appropriate supression rules local curState = curState; curState = db.pop_rules.whisper.alwaysOther and "other" or curState; if(WIM.db.pop_rules.whisper[curState].supress) then return true end elseif (frame._isWIM and ignore or block) then return true end -- Processes CHAT_MSG_SYSTEM events elseif (event == "CHAT_MSG_SYSTEM") then local msg = ...; local curState = db.pop_rules.whisper.alwaysOther and "other" or curState; for check, pattern in pairs(CMS_PATTERNS) do local user = FormatUserName(string.match(msg, pattern)); if (user) then local win = Windows[safeName(user)]; if (win) then -- error message if 'PLAYER_NOT_FOUND' == check or 'CHAT_IGNORED' == check then if (not frame._isWIM) then if(win:IsShown() and db.pop_rules.whisper[curState].supress or not win.msgSent) then return true; end else if win.online then win:AddMessage(msg, db.displayColors.errorMsg.r, db.displayColors.errorMsg.g, db.displayColors.errorMsg.b); win.online = false; end end -- system message elseif 'FRIEND_ONLINE' == check or 'FRIEND_OFFLINE' == check then if (not frame._isWIM) then if(win:IsShown() and db.pop_rules.whisper[curState].supress) then return true; end else if 'FRIEND_ONLINE' == check then msg = user.." ".._G.BN_TOAST_ONLINE win.online = true; else msg = user.." ".._G.BN_TOAST_OFFLINE win.online = false; end win:AddMessage(msg, db.displayColors.sysMsg.r, db.displayColors.sysMsg.g, db.displayColors.sysMsg.b); end end end -- no need to check remaining patterns break; end end end return false, ... end -- compatibility function for processing message event filters local function processMessageEventFilters(win, event, ...) local frame = win; -- if win is a WIM window, get its chat display frame if win and win.widgets and win.widgets.chat_display then -- ensure the chat display frame is the correct one frame = win.widgets.chat_display end -- if ChatFrameUtil is available, use its method for processing message event filters if (ChatFrameUtil and ChatFrameUtil.ProcessMessageEventFilters) then return ChatFrameUtil.ProcessMessageEventFilters(frame, event, ...); end local arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17 = ...; local chatFilters = _G.ChatFrame_GetMessageEventFilters(event); local filter = false; if ( chatFilters ) then local newarg1, newarg2, newarg3, newarg4, newarg5, newarg6, newarg7, newarg8, newarg9, newarg10, newarg11, newarg12, newarg13, newarg14; for _, filterFunc in pairs(chatFilters) do filter, newarg1, newarg2, newarg3, newarg4, newarg5, newarg6, newarg7, newarg8, newarg9, newarg10, newarg11, newarg12, newarg13, newarg14 = filterFunc(frame, event, ...); if ( filter ) then return true; elseif ( newarg1 ) then local _; arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14 = newarg1, newarg2, newarg3, newarg4, newarg5, newarg6, newarg7, newarg8, newarg9, newarg10, newarg11, newarg12, newarg13, newarg14; end end end return false, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17; end WhisperEngine.processMessageEventFilters = processMessageEventFilters; -- make accessible to other modules function WhisperEngine:CHAT_MSG_WHISPER(...) local arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17 = ...; arg2 = _G.Ambiguate(arg2, "none") local win, isNew = getWhisperWindowByUser(arg2, nil, nil, true); local filter, _; filter, arg1, _, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17 = processMessageEventFilters(win, 'CHAT_MSG_WHISPER', ...); if (filter and isNew) then win:close(); return true; end local color = WIM.db.displayColors.wispIn; -- color contains .r, .g & .b win.unreadCount = win.unreadCount and (win.unreadCount + 1) or 1; win:AddEventMessage(color.r, color.g, color.b, "CHAT_MSG_WHISPER", arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); win:Pop("in"); (ChatFrameUtil and ChatFrameUtil.SetLastTellTarget or _G.ChatEdit_SetLastTellTarget)(arg2, "WHISPER"); win.online = true; updateMinimapAlerts(); -- get missing data available from C_PlayerInfo if (arg12 and (not win.race or win.class)) then local class, _, race = GetPlayerInfoByGUID(arg12); win.WhoCallback({ Name = win.theUser, Online = true, Guild = win.guild, Class = class or win.class, Level = win.level, Race = race or win.race, Zone = win.location }); end -- emulate blizzards flash client icon behavior. if FlashClientIcon then FlashClientIcon(); end CallModuleFunction("PostEvent_Whisper", arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); end function WhisperEngine:CHAT_MSG_WHISPER_INFORM(...) local arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17 = ...; arg2 = _G.Ambiguate(arg2, "none") local win, isNew = getWhisperWindowByUser(arg2, nil, nil, true); local filter, _; filter, arg1, _, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17 = processMessageEventFilters(win, 'CHAT_MSG_WHISPER_INFORM', ...); if (filter and isNew) then win:close(); return true; end local color = db.displayColors.wispOut; -- color contains .r, .g & .b win:AddEventMessage(color.r, color.g, color.b, "CHAT_MSG_WHISPER_INFORM", arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); win.unreadCount = 0; -- having replied to conversation implies the messages have been read. win:Pop("out"); (ChatFrameUtil and ChatFrameUtil.SetLastTellTarget or _G.ChatEdit_SetLastTellTarget)(arg2, "WHISPER"); win.online = true; win.msgSent = false; updateMinimapAlerts(); CallModuleFunction("PostEvent_WhisperInform", arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); addToTableUnique(recentSent, arg1); if(#recentSent > maxRecent) then table.remove(recentSent, 1); end end function WhisperEngine:CHAT_MSG_BN_WHISPER_INFORM(...) local arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17 = ...; local win, isNew = getWhisperWindowByUser(arg2, true, arg13, true); if not win then return end --due to a client bug, we can not receive the other player's name, so do nothing local filter, _; filter, arg1, _, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17 = processMessageEventFilters(win, 'CHAT_MSG_BN_WHISPER_INFORM', ...); if (filter and isNew) then win:close(); return true; end local color = db.displayColors.BNwispOut; -- color contains .r, .g & .b if not win then return end --due to a client bug, we can not receive the other player's name, so do nothing win:AddEventMessage(color.r, color.g, color.b, "CHAT_MSG_BN_WHISPER_INFORM", arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); win.unreadCount = 0; -- having replied to conversation implies the messages have been read. win:Pop("out"); (ChatFrameUtil and ChatFrameUtil.SetLastTellTarget or _G.ChatEdit_SetLastTellTarget)(arg2, "BN_WHISPER"); win.online = true; win.msgSent = false; updateMinimapAlerts(); -- emulate blizzards flash client icon behavior. if FlashClientIcon then FlashClientIcon(); end CallModuleFunction("PostEvent_WhisperInform", arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); addToTableUnique(recentSent, arg1); if(#recentSent > maxRecent) then table.remove(recentSent, 1); end end function WhisperEngine:CHAT_MSG_BN_WHISPER(...) local arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17 = ...; local win, isNew = getWhisperWindowByUser(arg2, true, arg13, true); if not win then return end --due to a client bug, we can not receive the other player's name, so do nothing local filter, _; filter, arg1, _, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17 = processMessageEventFilters(win, 'CHAT_MSG_BN_WHISPER', ...); if (filter and isNew) then win:close(); return true; end local color = WIM.db.displayColors.BNwispIn; -- color contains .r, .g & .b win.unreadCount = win.unreadCount and (win.unreadCount + 1) or 1; win:AddEventMessage(color.r, color.g, color.b, "CHAT_MSG_BN_WHISPER", arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); win:Pop("in"); (ChatFrameUtil and ChatFrameUtil.SetLastTellTarget or _G.ChatEdit_SetLastTellTarget)(arg2, "BN_WHISPER"); win.online = true; updateMinimapAlerts(); CallModuleFunction("PostEvent_Whisper", arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); end function WhisperEngine:CHAT_MSG_AFK(...) local color = db.displayColors.wispIn; -- color contains .r, .g & .b local win = Windows[safeName(select(2, ...))]; if(win) then win:AddEventMessage(color.r, color.g, color.b, "CHAT_MSG_AFK", ...); win:Pop("out"); (ChatFrameUtil and ChatFrameUtil.SetLastTellTarget or _G.ChatEdit_SetLastTellTarget)(select(2, ...), "AFK"); win.online = true; end end function WhisperEngine:CHAT_MSG_DND(...) local color = db.displayColors.wispIn; -- color contains .r, .g & .b local win = Windows[safeName(select(2, ...))]; if(win) then win:AddEventMessage(color.r, color.g, color.b, "CHAT_MSG_AFK", ...); win:Pop("out"); (ChatFrameUtil and ChatFrameUtil.SetLastTellTarget or _G.ChatEdit_SetLastTellTarget)(select(2, ...), "AFK"); win.online = true; end end local CMS_SLUG = {}; function WhisperEngine:CHAT_MSG_SYSTEM(...) -- the proccessing of the actual message is taking place within the ChatMessageFilter CMS_SLUG._isWIM = true; processMessageEventFilters(CMS_SLUG, 'CHAT_MSG_SYSTEM', ...); end function WhisperEngine:CHAT_MSG_BN_INLINE_TOAST_ALERT(process, playerName, languageName, channelName, playerName2, specialFlags, zoneChannelID, channelIndex, channelBaseName, unused, lineID, guid, bnSenderID, isMobile, isSubtitle, hideSenderInLetterbox, supressRaidIcons) local online = process == "FRIEND_ONLINE" local offline = process == "FRIEND_OFFLINE" local curState = db.pop_rules.whisper.alwaysOther and "other" or curState; local _, accName = GetBNGetFriendInfoByID(bnSenderID) local win = Windows[safeName(accName)] if win then local msg = accName.." "..(online and _G.BN_TOAST_ONLINE or offline and _G.BN_TOAST_OFFLINE or "") win:AddMessage(msg, db.displayColors.sysMsg.r, db.displayColors.sysMsg.g, db.displayColors.sysMsg.b); win.online = online; return; end end -------------------------------------- -- Whisper Related Hooks -- -------------------------------------- local function replyTellTarget(TellNotTold) if (db.enabled) then local lastTell, lastTellType; local curState = db.pop_rules.whisper.alwaysOther and "other" or curState; if (TellNotTold) then lastTell, lastTellType = (ChatFrameUtil and ChatFrameUtil.GetLastTellTarget and ChatFrameUtil.GetLastTellTarget or _G.ChatEdit_GetLastTellTarget)(); else lastTell, lastTellType = (ChatFrameUtil and ChatFrameUtil.GetLastToldTarget and ChatFrameUtil.GetLastToldTarget or _G.ChatEdit_GetLastToldTarget)(); end -- Grab the string after the slash command if not lastTell then return end--because if you fat finger R or try to re ply before someone sent a tell, it generates a lua error without this if (lastTell ~= "" and db.pop_rules.whisper.intercept) then lastTell = _G.Ambiguate(lastTell, "none") local bNetID; if (lastTellType == "BN_WHISPER" or lastTell:find("^|K")) then bNetID = _G.BNet_GetBNetIDAccount(lastTell); end local win = getWhisperWindowByUser(lastTell, bNetID and true, bNetID); if not win then return end if (win and win:IsVisible() or db.pop_rules.whisper[curState].onSend) then win.widgets.msg_box.setText = 1; win:Pop(true); -- force popup win.widgets.msg_box:SetFocus(); local eb = getVisibleChatFrameEditBox(); if _G.ChatFrameEditBoxMixin and _G.ChatFrameEditBoxMixin.ClearChat then (getVisibleChatFrameEditBox() or _G.ChatFrame1EditBox):ClearChat(); else _G.ChatEdit_OnEscapePressed(getVisibleChatFrameEditBox() or _G.ChatFrame1EditBox); end end end end end function CF_SentBNetTell(target) if (db and db.enabled) then local curState = curState; curState = db.pop_rules.whisper.alwaysOther and "other" or curState; if (db.pop_rules.whisper.intercept and db.pop_rules.whisper[curState].onSend) then local bNetID = _G.BNet_GetBNetIDAccount(target); target = _G.Ambiguate(target, "none")--For good measure, ambiguate again cause it seems some mods interfere with this process local win = getWhisperWindowByUser(target, true, bNetID); if not win then return end --due to a client bug, we can not receive the other player's name, so do nothing win.widgets.msg_box.setText = 1; win:Pop(true); -- force popup win.widgets.msg_box:SetFocus(); local editBox = _G.LAST_ACTIVE_CHAT_EDIT_BOX; if (editBox) then if _G.ChatFrameEditBoxMixin and _G.ChatFrameEditBoxMixin.OnEscapePressed then _G.ChatFrameEditBoxMixin.OnEscapePressed(editBox) else _G.ChatEdit_OnEscapePressed(editBox); end return; end end end end if ChatFrameUtil and ChatFrameUtil.SendBNetTell then hooksecurefunc(ChatFrameUtil, "SendBNetTell", CF_SentBNetTell); else hooksecurefunc("ChatFrame_SendBNetTell", CF_SentBNetTell); end --Hook ChatFrame_ReplyTell & ChatFrame_ReplyTell2 if ChatFrameUtil and ChatFrameUtil.ReplyTell then hooksecurefunc(ChatFrameUtil, "ReplyTell", function() replyTellTarget(true) end); hooksecurefunc(ChatFrameUtil, "ReplyTell2", function() replyTellTarget(false) end); else hooksecurefunc("ChatFrame_ReplyTell", function() replyTellTarget(true) end); hooksecurefunc("ChatFrame_ReplyTell2", function() replyTellTarget(false) end); end -- hook SendChatMessage to track sent messages if _G.C_ChatInfo and _G.C_ChatInfo.SendChatMessage then hooksecurefunc(_G.C_ChatInfo, "SendChatMessage", function(...) if(select(2, ...) == "WHISPER") then local win = Windows[safeName(FormatUserName(select(4, ...))) or "NIL"]; if(win) then win.msgSent = true; end end end); else -- legacy SendChatMessage hook local hookedSendChatMessage = _G.SendChatMessage; function _G.SendChatMessage(...) if(select(2, ...) == "WHISPER") then local win = Windows[safeName(FormatUserName(select(4, ...))) or "NIL"]; if(win) then win.msgSent = true; end end hookedSendChatMessage(...); end end local function processChatType(editBox, msg, index, send) local target, chatType, targetFound, parsedMsg; -- whispers if (index == "WHISPER" or index == "SMART_WHISPER") then targetFound, target, chatType, parsedMsg = (editBox.ExtractTellTarget or _G.ChatEdit_ExtractTellTarget)(editBox, msg, index); if not targetFound then return end -- reply elseif (index == "REPLY") then target, chatType = (ChatFrameUtil and ChatFrameUtil.GetLastTellTarget or _G.ChatEdit_GetLastTellTarget)(); if not target then return end -- other unsupported else return end -- handle the whisper interception if (target and db and db.enabled) then local curState = curState; curState = db.pop_rules.whisper.alwaysOther and "other" or curState; if (db.pop_rules.whisper.intercept and db.pop_rules.whisper[curState].onSend) then -- target = _G.Ambiguate(target, "none")--For good measure, ambiguate again cause it seems some mods interfere with this process local bNetID = nil; if chatType == "BN_WHISPER" then bNetID = _G.BNet_GetBNetIDAccount(target); end local win = getWhisperWindowByUser(target, bNetID and true, bNetID); if not win then return end --due to a client bug, we can not receive the other player's name, so do nothing win.widgets.msg_box.setText = 1; win:Pop(true); -- force popup win.widgets.msg_box:SetFocus(); if _G.ChatFrameEditBoxMixin and _G.ChatFrameEditBoxMixin.ClearChat then -- editBox:ClearChat(); editBox:SetText(""); editBox:Hide(); else _G.ChatEdit_OnEscapePressed(editBox); end end end end -- ChatEditBoxMixin hooking if ChatFrameUtil and ChatFrameUtil.ActivateChat then -- each time a chat edit box is activated, check if it is hooked accordingly. hooksecurefunc(ChatFrameUtil, "ActivateChat", function(editBox) -- first check that the editBox is not WIM's msg_box, if it is, then do nothing. if(editBox._WIM_WhisperEngine_Hooked or editBox.widgetName == "msg_box") then return; end hooksecurefunc(editBox, "ProcessChatType", processChatType); -- mark it as hooked editBox._WIM_WhisperEngine_Hooked = true; end); end hooksecurefunc("AutoCompleteButton_OnClick", function(self) local autoComplete = self:GetParent(); local editBox = autoComplete.parent; local target = self.nameInfo and self.nameInfo.name and safeName(_G.Ambiguate(self.nameInfo.name, "none")) or nil; -- only proceed if the editBox is a whisper type if (not editBox or (editBox:GetAttribute("chatType") ~= "WHISPER" and editBox:GetAttribute("chatType") ~= "BN_WHISPER")) then return; end -- handle the whisper interception if (target and db and db.enabled) then local curState = curState; curState = db.pop_rules.whisper.alwaysOther and "other" or curState; if (db.pop_rules.whisper.intercept and db.pop_rules.whisper[curState].onSend) then local bNetID = self.nameInfo.bnetID; local win = getWhisperWindowByUser(target, bNetID and true, bNetID); if not win then return end --due to a client bug, we can not receive the other player's name, so do nothing win.widgets.msg_box.setText = 1; win:Pop(true); -- force popup win.widgets.msg_box:SetFocus(); if _G.ChatFrameEditBoxMixin and _G.ChatFrameEditBoxMixin.ClearChat then editBox:ClearChat(); else _G.ChatEdit_OnEscapePressed(editBox); end end end end); -- Legacy hooks if not _G.ChatFrameEditBoxBaseMixin or not _G.ChatFrameEditBoxBaseMixin.ExtractTellTarget then hooksecurefunc("ChatEdit_HandleChatType", function(self, msg, command, send) local channel = _G.strmatch(command, "/([0-9]+)"); if not channel then local index = _G.hash_ChatTypeInfoList[command]; processChatType(self, msg, index, send); end end); end -- global reference GetWhisperWindowByUser = getWhisperWindowByUser; -- define context menu local info = {}; info.text = "MENU_MSGBOX"; local msgBoxMenu = AddContextMenu(info.text, info); info = {}; info.text = WIM.L["Recently Sent Messages"]; info.notCheckable = true; msgBoxMenu:AddSubItem(AddContextMenu("RECENT_LIST", info), 1); local recentMenu = GetContextMenu("RECENT_LIST"); if(recentMenu.menuTable) then for k, _ in pairs(recentMenu.menuTable) do recentMenu.menuTable[k] = nil; end end for i=1, maxRecent do info = GetContextMenu("RECENT_LIST"..i) or {}; info.txt = " "; info.hidden = true; info.notCheckable = true; recentMenu:AddSubItem(AddContextMenu("RECENT_LIST"..i, info)); end local function recentMenuClick(self) libs.DropDownMenu.CloseDropDownMenus(); if(MSG_CONTEXT_MENU_EDITBOX) then if(_G.IsShiftKeyDown()) then MSG_CONTEXT_MENU_EDITBOX:Insert(self.value); else MSG_CONTEXT_MENU_EDITBOX:SetText(self.value); end end end RegisterWidgetTrigger("msg_box", "whisper,chat,w2w", "OnMouseDown", function(self) if(#recentSent == 0) then local item = GetContextMenu("RECENT_LIST1"); item.text = "|cff808080 - "..L["None"].." - |r"; item.notClickable = true; item.hidden = nil; return; end for i=maxRecent, 1, -1 do local item = GetContextMenu("RECENT_LIST"..(10-i+1)); item.notClickable = nil; if(recentSent[i]) then item.text = recentSent[i]; item.value = recentSent[i]; item.func = recentMenuClick; item.hidden = nil; else item.hidden = true; end end end); -- This is a core module and must always be loaded... WhisperEngine.canDisable = false;