My goal is to block repeated messages from the same sender, even if they're in different channels. Prior to 2.4, I hooked ChatFrame_MessageEventHandler. In 2.4, that didn't seem to work anymore, so I used the newly added ChatFrame_AddMessageEventFilter. Now 3.0 has come along and demolished that too, because there's no more global "arg2" containing the sender's name that I can use in my filter function, but the actual message text is the only argument passed to the filter function. I tried hooking CF_MEH again; no dice.
Anyone have a creative (or not so creative) way to filter messages based on criteria other than, or in addition to, the message itself?
Not sure what you're talking about, I use it fine in BadBoy. Make sure you use the global arg2. The only thing you can make local is arg1, doing it for everything else breaks it.
ChatFrame_AddMessageEventFilter is wonderful. Example:
local function filter(msg)
if msg:match(L["(.*) won: (.+)"]) or msg:match(L["(.*) has?v?e? selected (.+) for: (.+)"]) or msg:match(L["(.+) Roll . (%d+) for (.+) by (.+)"])
or msg:match(L["You passed on: "]) or msg:match(L[" automatically passed on: "]) or (msg:match(L[" passed on: "]) and not msg:match(L["Everyone passed on: "])) then
Debug("Supressing chat message", msg)
return true
end
end
ChatFrame_AddMessageEventFilter("CHAT_MSG_LOOT", filter)
Granted, I don't fuck with the args, so I'm probably no help.
Yeah, that's basically what I'm doing, except that in addition to the message I need the sender's name. What I have isn't throwing any errors, but last night some jackass was spamming /yells and I noticed it wasn't working to suppress them. I'll look at your code in BadBoy, Funky, and compare against mine when I get home. I don't think I'm using a local, but it's possible, I suppose. Thanks.
That's weird, I can still access global "arg2" and "this" inside the handler.
I typed the code below in a Hack page and executed, then "/say test" and the result was:
FOUND ChatFrame1 Ara nil
FOUND ChatFrame3 Ara nil
That's an improvement: before, it was always processed 7 times, even with just one chat tab. But it's still far from perfect and to optimize you still have to store the result on "this==ChatFrame1" and return the stored result on other char frames, to avoid processing more than once.
local function filter(msg)
if msg == "test" then
print( "FOUND", this and this:GetName() or "nil", arg2 or "nil", self or "nil" )
return true
end
end
ChatFrame_AddMessageEventFilter("CHAT_MSG_SAY", filter)
That's weird, I can still access global "arg2" and "this" inside the handler.
I typed the code below in a Hack page and executed, then "/say test" and the result was:
FOUND ChatFrame1 Ara nil
FOUND ChatFrame3 Ara nil
That's an improvement: before, it was always processed 7 times, even with just one chat tab. But it's still far from perfect and to optimize you still have to store the result on "this==ChatFrame1" and return the stored result on other char frames, to avoid processing more than once.
local function filter(msg)
if msg == "test" then
print( "FOUND", this and this:GetName() or "nil", arg2 or "nil", self or "nil" )
return true
end
end
ChatFrame_AddMessageEventFilter("CHAT_MSG_SAY", filter)
Use "self" instead of "this". As to the arg2 problem, read what Funkeh said
There's a reason my sample includes "self", it's to show that weird behavior of "self" returning "nil" and "this" returning what "self" should have returned :)
As for arg2, it's been over a year now that I use it in my own chat.
"this" instead of "self" was correct there. There is no global "self" and it doesn't seem like ChatFrame_AddMessageEventFilter has been updated to pass more than arg1. So expected behavior is that self would return nil.
Correct, you ONLY get the message in your callbacks. Nothing else. I would have hoped that they added arg2--argn, since it's literally 4 letters they have to add to FrameXML, but they have not done so.
So, yeah, access the globals.
If you want to try to be future-safe, you could do something like:
local function myFilter(a1,a2,a3,a4,a5)
a2 = a2 or arg2
a3 = a3 or arg3
a4 = a4 or arg4
... and chances are that it'll work when the globals are nuked and you get everything via args.
It never has been the case. The only problem that has ever existed, and will continue to exist for as long as Lua scoping works the way it does today is:
arg1="THIS IS GLOBAL"
function foo(arg1)
print(arg1)
[I]>> "hi mom"[/I]
print(_G.arg1)
[I]>> "THIS IS GLOBAL"[/I]
end
foo("hi mom")
Note how I un-cleverly used the exact same variable names: "arg1" and "arg1". "whatever1" or "a1" will never ever hide "arg1". This is standard argument passing / scoping rules and works the same way in pretty much any language that exists. "arg1" is not a magical entity that blizzard summoned from the depths of the twisting nether. It is just a global variable that gets set as events are fired, subject to the same rules as everything else.
We're talking about early blizzard's script code. Common pratice doesn't apply here :)
If you stay into their pre BC code for too long, you will easily forget how to do a simple table lookup and go crazy with 400 gsub. Seriouly :) Take a look at FrameXML\ChatFrame.lua:2217
function ChatFrame_MessageEventHandler(self, event, ...)
if ( strsub(event, 1, 8) == "CHAT_MSG" ) then
local arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11 = ...;
local type = strsub(event, 10);
local info = ChatTypeInfo[type];
local filter, newarg1 = false;
if ( chatFilters[event] ) then
for _, filterFunc in next, chatFilters[event] do
filter, newarg1 = filterFunc(arg1);
arg1 = (newarg1 or arg1);
if ( filter ) then
return true;
end
end
end
local channelLength = strlen(arg4);
if ( (strsub(type, 1, 7) == "CHANNEL") and (type ~= "CHANNEL_LIST") and ((arg1 ~= "INVITE") or (type ~= "CHANNEL_NOTICE_USER")) ) then
if ( arg1 == "WRONG_PASSWORD" ) then
local staticPopup = getglobal(StaticPopup_Visible("CHAT_CHANNEL_PASSWORD") or "");
if ( staticPopup and staticPopup.data == arg9 ) then
-- Don't display invalid password messages if we're going to prompt for a password (bug 102312)
return;
end
end
local found = 0;
for index, value in pairs(self.channelList) do
if ( channelLength > strlen(value) ) then
-- arg9 is the channel name without the number in front...
if ( ((arg7 > 0) and (self.zoneChannelList[index] == arg7)) or (strupper(value) == strupper(arg9)) ) then
found = 1;
info = ChatTypeInfo["CHANNEL"..arg8];
if ( (type == "CHANNEL_NOTICE") and (arg1 == "YOU_LEFT") ) then
self.channelList[index] = nil;
self.zoneChannelList[index] = nil;
end
break;
end
end
end
if ( (found == 0) or not info ) then
return true;
end
end
if ( type == "SYSTEM" or type == "TEXT_EMOTE" or type == "SKILL" or type == "LOOT" or type == "MONEY" or
type == "OPENING" or type == "TRADESKILLS" or type == "PET_INFO" ) then
self:AddMessage(arg1, info.r, info.g, info.b, info.id);
elseif ( strsub(type,1,7) == "COMBAT_" ) then
self:AddMessage(arg1, info.r, info.g, info.b, info.id);
elseif ( strsub(type,1,6) == "SPELL_" ) then
self:AddMessage(arg1, info.r, info.g, info.b, info.id);
elseif ( strsub(type,1,10) == "BG_SYSTEM_" ) then
self:AddMessage(arg1, info.r, info.g, info.b, info.id);
elseif ( strsub(type,1,11) == "ACHIEVEMENT" ) then
self:AddMessage(arg1, info.r, info.g, info.b, info.id);
elseif ( strsub(type,1,18) == "GUILD_ACHIEVEMENT" ) then
self:AddMessage(arg1, info.r, info.g, info.b, info.id);
elseif ( type == "IGNORED" ) then
self:AddMessage(format(CHAT_IGNORED, arg2), info.r, info.g, info.b, info.id);
elseif ( type == "FILTERED" ) then
self:AddMessage(format(CHAT_FILTERED, arg2), info.r, info.g, info.b, info.id);
elseif ( type == "RESTRICTED" ) then
self:AddMessage(CHAT_RESTRICTED, info.r, info.g, info.b, info.id);
elseif ( type == "CHANNEL_LIST") then
if(channelLength > 0) then
self:AddMessage(format(getglobal("CHAT_"..type.."_GET")..arg1, tonumber(arg8), arg4), info.r, info.g, info.b, info.id);
else
self:AddMessage(arg1, info.r, info.g, info.b, info.id);
end
elseif (type == "CHANNEL_NOTICE_USER") then
if(strlen(arg5) > 0) then
-- TWO users in this notice (E.G. x kicked y)
self:AddMessage(format(getglobal("CHAT_"..arg1.."_NOTICE"), arg8, arg4, arg2, arg5), info.r, info.g, info.b, info.id);
elseif ( arg1 == "INVITE" ) then
self:AddMessage(format(getglobal("CHAT_"..arg1.."_NOTICE"), arg4, arg2), info.r, info.g, info.b, info.id);
else
self:AddMessage(format(getglobal("CHAT_"..arg1.."_NOTICE"), arg8, arg4, arg2), info.r, info.g, info.b, info.id);
end
elseif (type == "CHANNEL_NOTICE") then
...
Dont try to hang youself afterward, and tell me "it's well done" :)
PS: I only pasted half of the function, but the second part is unimaginably worse like this (line 2347):
body = format(getglobal("CHAT_"..type.."_GET")..languageHeader..arg1, pflag.."|Hplayer:"..arg2..":"..arg11.."|h".."["..arg2.."]".."|h");
It's hilarous to find 13 ".." concat (which means 13 string created) when he seems to know that format exists xD
Sorry for this bad mouthing, but it's good to laugh some times :p
It's hilarous to find 13 ".." concat (which means 13 string created) when he seems to know that format exists xD
Nitpickery: The ".." operator becomes a single huge concat operation without intermediate string garbage -- assuming all the components are strings. If they are nonstrings they get converted before the concatenation.
(Yes, rly, we tested :))
So, for all-strings: Use "..". It's slightly faster than using format().
If nonstrings are involved, use format(), it doesn't create extra garbage.
Nitpickery: The ".." operator becomes a single huge concat operation without intermediate string garbage -- assuming all the components are strings. If they are nonstrings they get converted before the concatenation.
(Yes, rly, we tested :))
So, for all-strings: Use "..". It's slightly faster than using format().
If nonstrings are involved, use format(), it doesn't create extra garbage.
Interesting. So when comparing the following 2 pieces of code (colorXXX are strings), the latter the better in all aspects ?
For the sake of readability, the latter option is better; string1 .. "" .. string2 (which is effectively what you're doing with strjoin()) is more readable than strjoin("", string1, string2).
For the sake of garbage, the latter is better; the function strjoin() contains a local variable, ret (maybe), that is used to concatenate the "..." arguments which creates garbage on every call.
And for the sake of speed, the latter is better; the strjoin() function looks something like this:
-- strjoin() is actually an alias of this function
string.join = function(delim, ...)
local ret = select(1, ...)
if ret then
for i = 2, select("#", ...) do
ret = delim .. select(i, ...)
end
end
return ret
end
-- this function calls the above function
function strjoin(...) return string.join(...) end
It's pretty obvious when you look at what the function actually does to make a determination on which way is better.
Also, the strjoin() function should be used for combining multiple strings with a real delimeter not an empty string, such as strjoin(":", hour, minute, second) or strjoin("/", month, day, year) or strjoin("/", folder1, folder2, folder3, filename).
First, NEVER HOOK ChatFrame_OnEvent! Hook the function that it calls that you are interested in. The first function it calls can't be tainted. I'm not sure about the second function it calls. The third function can be safely tainted.
Second, in 3.0.2 the global args on events are still available to be used in functions such as ChatFrame_AddMessageEventFilter, I'm not sure about 3.0.3 but I doubt its changed.
Anyone have a creative (or not so creative) way to filter messages based on criteria other than, or in addition to, the message itself?
Granted, I don't fuck with the args, so I'm probably no help.
I typed the code below in a Hack page and executed, then "/say test" and the result was:
FOUND ChatFrame1 Ara nil
FOUND ChatFrame3 Ara nil
That's an improvement: before, it was always processed 7 times, even with just one chat tab. But it's still far from perfect and to optimize you still have to store the result on "this==ChatFrame1" and return the stored result on other char frames, to avoid processing more than once.
Use "self" instead of "this". As to the arg2 problem, read what Funkeh said
There's a reason my sample includes "self", it's to show that weird behavior of "self" returning "nil" and "this" returning what "self" should have returned :)
As for arg2, it's been over a year now that I use it in my own chat.
Sorry if I wasn't clear, I was replying to Phanx.
So, yeah, access the globals.
If you want to try to be future-safe, you could do something like:
... and chances are that it'll work when the globals are nuked and you get everything via args.
Stop hitting the glue bottle so deep and read my code. The locals are called a1..aN, the globals are called arg1..argN. They do not interfere.
His point was: whatever1..whateverN could hide arg1..argN.
But I just tested and it's not (or no longer) the case.
Note how I un-cleverly used the exact same variable names: "arg1" and "arg1". "whatever1" or "a1" will never ever hide "arg1". This is standard argument passing / scoping rules and works the same way in pretty much any language that exists. "arg1" is not a magical entity that blizzard summoned from the depths of the twisting nether. It is just a global variable that gets set as events are fired, subject to the same rules as everything else.
If you stay into their pre BC code for too long, you will easily forget how to do a simple table lookup and go crazy with 400 gsub. Seriouly :) Take a look at FrameXML\ChatFrame.lua:2217
Dont try to hang youself afterward, and tell me "it's well done" :)
PS: I only pasted half of the function, but the second part is unimaginably worse like this (line 2347):
It's hilarous to find 13 ".." concat (which means 13 string created) when he seems to know that format exists xD
Sorry for this bad mouthing, but it's good to laugh some times :p
Nitpickery: The ".." operator becomes a single huge concat operation without intermediate string garbage -- assuming all the components are strings. If they are nonstrings they get converted before the concatenation.
(Yes, rly, we tested :))
So, for all-strings: Use "..". It's slightly faster than using format().
If nonstrings are involved, use format(), it doesn't create extra garbage.
Interesting. So when comparing the following 2 pieces of code (colorXXX are strings), the latter the better in all aspects ?
For the sake of garbage, the latter is better; the function strjoin() contains a local variable, ret (maybe), that is used to concatenate the "..." arguments which creates garbage on every call.
And for the sake of speed, the latter is better; the strjoin() function looks something like this: It's pretty obvious when you look at what the function actually does to make a determination on which way is better.
Also, the strjoin() function should be used for combining multiple strings with a real delimeter not an empty string, such as strjoin(":", hour, minute, second) or strjoin("/", month, day, year) or strjoin("/", folder1, folder2, folder3, filename).
I see little other choice than to hook ChatFrame_OnEvent (or something like that). :-/
Second, in 3.0.2 the global args on events are still available to be used in functions such as ChatFrame_AddMessageEventFilter, I'm not sure about 3.0.3 but I doubt its changed.
CF_MEH will call your filter, then you can just reference the args you saved.