-- 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 ;