WARNING: This ended up being a very long post with lots of information. Read at your own peril.
While my typical response might have been read the documentation, unfortunately the documentation isn’t completely clear so you have to make assumptions based on the wording and I can’t assume that you’ll know how to interpret the wording either. Anyhow: RegisterChatCallback documentation.
I will explain from the top.
The way chat callbacks works are two-way: Roblox uses InvokeChatCallback, you use RegisterChatCallback. InvokeChatCallback is irrelevant to you (don’t call it, don’t think you can anyway), so focus on RegisterChatCallback. This will allow you to have a certain function ran when Roblox calls InvokeChatCallback.
For every use of RegisterChatCallback, you will always have the following two commonalities and differences at minimum:
Common:
-
Two parameters, the first is a ChatCallbackType Enum and the second is a function
-
The function you pass must return something
Different:
-
Roblox will call InvokeChatCallback with different arguments for different types of ChatCallbackTypes. You need to account for that. You can find out what by reading the documentation or reading through the LCS source code.
-
What you need to return will differ depending on the circumstances. Again, also explained in the documentation but no code samples are given so novice developers working with the LCS may have trouble here.
For OnServerReceivingMessage, Roblox will pass a ChatMessage object as a parameter. This will allow you to process the unfiltered message your own way first. In your function, you will only need the parameter messageObj
(this is canonical if you want to adhere to LCS conventions). In code format, you’ll have a setup like this:
-- I'm applying my own conventions so you can see it easier
local SERVER_RECEIVE_MESSAGE = Enum.ChatCallbackType.OnServerReceivingMessage
local function serverMessageCallback(messageObj)
-- Some code here
end
ChatService:RegisterChatCallback(SERVER_RECEIVE_MESSAGE, serverMessageCallback)
OnServerReceivingMessage chat callbacks will allow you to, from the server, make changes to the message object. Valid changes are available in the ChatMessage object documentation, such as changing what the message is or even its extra data (chat tags, chat colour, etc). There is one property that you can set as well which isn’t documented: ShouldDeliver. Setting it to false will prevent the server from sending the message. You can use this for things like chat blacklists.
All that clear? Great, so now let’s go into addressing your actual problem, which is just confusion on how to use this chat callback. I could just give you the solution, but if you don’t have that knowledge prior, things could stay confusing. It’s always good to pick up a thing or two when you resolve problems for future reference.
First off, get rid of the entire PlayerAdded connection, you won’t need that. Don’t ever use InvokeChatCallback by yourself, the Lua Chat System will do it for you.
Now, you should be left with two parts: the module’s code (your FilterService) and the callback registration code. We’ll address the callback registration code first because it’s easier and quicker to resolve, plus I gave an entire information TED talk about this callback.
Using the information gained from above, you’ll update your relay function to receive the messageObj as well as to return it back. Just a few simple changes. Your code should end up like this:
(Pre-code note: I use RobloxChatService for GetService-Chat so it doesn’t conflict with the ChatService module from the Lua Chat System.)
local FilterService = require(game:GetService("ServerScriptService").CustomFilterService)
local RobloxChatService = game:GetService("Chat")
local function relay(messageObj)
-- We'll send the raw message over to your FilterService to maintain its
-- versatility beyond just being for the Lua Chat System.
local filteredMessage = FilterService:Filter(messageObj.Message)
-- With the newly filtered message, we will update the message and
-- have this given back to the LCS.
messageObj.Message = filteredMessage
return messageObj
end
ChatService:RegisterChatCallback(Enum.ChatCallbackType.OnServerReceivingMessage, relay)
Now, like I said earlier, while resolving this problem I want to retain the versatility of your FilterService so I don’t end up suggesting a change that’ll make it incompatible with any other uses for it you have (non-chat system filter requirements).
To explain what I did here: I sent the message over to FilterService:Filter to be processed as it deals with strings, which will then be given back to the relay function after processing. It will update the messageObj with the new filtered message and give all of this back to the chat system.
Now some changes for _C:Filter. First, remove the player parameter. It is possible to get a player from a messageObj but do remember that the Lua Chat System also allows NPC speakers, so a ChatSpeaker may not necessarily have a player.
The next thing you want to do is make a copy of the string rather than to operate on the message passed as an argument. Your current code will convert the given message into all lowercase letters to do the filter. That means that if your code doesn’t flag the message, what will be given back is a lowercase string, meaning anything ever sent in your game will be lowercase.
Remember, with Lua, changes aren’t made directly on a string, a new string is generated with the changes made. So we can put that new string into a variable and operate on that instead. From there, another boolean can accompany us to determine if the message was filtered or not. We can either return the flagged message default or the raw message if it wasn’t flagged.
function _C:Filter(message)
-- Make changes to a proxy
local messageCopy = message:lower()
local wasFlagged = false
-- If this isn't a dictionary, use ipairs instead
for _, filteredWord in pairs(Filter) do
-- Find filtered words in the proxy string: lower the current
-- filter word in iteration just in case as well if you forget
-- to make one lowercase.
if messageCopy:find(filteredWord:lower()) then
wasFlagged = true
-- This loop doesn't need to run anymore the moment a true
-- result is found, so prematurely terminate it.
break
end
end
-- You can use Lua ternary here, but for others to understand better,
-- I will use if statements to better show what's up.
if wasFlagged then
-- Give back a string about how the message was flagged. If you review
-- the code in relay, this effectively becomes the new message.
return "[flagged for inappropriate content.]"
else
-- Give back the original message.
return message
end
end
That should be all to it. Try this stuff out, do some debugging if it doesn’t work or give me a nice green mark, the thing we all know and love.
Wait though. Can’t send you off just yet, because there’s still a chance for me to sneak some learning in here. Remember that chat bots send messages and that your filter will just replace the message out. The message you write can still be flagged by Roblox’s filter and you’ll still have spam in your chat bar. For UX’s sake, I prefer to outright decline sending the message.
Remember the undocumented property I talked about earlier, ShouldDeliver? We can use that to our advantage. By preventing the message from actually ever getting sent in the chat window, not only will you have blocked the scam message from appearing but players won’t have their chat clogged up with “flagged” which would be equally as annoying as seeing the scam message in the first place. Bonus round: Chatted still fires, meaning accounts can be moderated if reported.
If you prefer to take this route (and I strongly encourage you do!), you’ll have to change up your filter a bit or make a new function. It can be non-returning, we’ll just be calling this to determine if we should add in the ShouldDeliver property to the message object. I’ll write from the perspective of fundamentally rewriting Filter but you have the option to make this a separate function.
So, we can still reuse the Filter function written earlier, except this time we’ll return wasFlagged regardless of what happens to it and we’ll also change when it becomes true/false. Remember, ShouldDeliver = false means it won’t send. _C:Filter will now look like this:
(Pre-code note: would advise a new named function so it’s clear what the intention of the function is. For example, CheckShouldDeliver
, so that you can get the nice line of local shouldDeliver = FilterService:CheckShouldFilter(messageObj.Message)
.)
function _C:Filter(message)
local messageCopy = message:lower()
-- We make this shouldDeliver now
local shouldDeliver = true
for _, filteredWord in pairs(Filter) do
if messageCopy:find(filteredWord:lower()) then
-- Flagged word will make this false instead
shouldDeliver = false
break
end
end
return shouldDeliver
end
Now back to relay. What we’re going to do is run the message from messageObj through this newly (re)written function to get back a boolean which will determine if we should put a ShouldDeliver property in the messageObj. We’ll then give that back to the LCS which will, if it finds ShouldDeliver to be false, drop the message from the send queue. Relay will become this:
local function relay(messageObj)
-- We'll continue to send the message, but this time what we want
-- back is not a string but a boolean.
local shouldDeliver = FilterService:Filter(messageObj.Message)
-- Remember: in Lua, keys still exist in tables but are nil. The way LCS
-- checks is explicitly if ShouldDeliver is false. So, keeping in mind
-- both how Lua can read table indices and how LCS processes
-- the ShouldDeliver key, we can indiscriminately set the key.
messageObj.ShouldDeliver = shouldDeliver
-- Once again, give the messageObj back, this time though we only allow
-- the LCS to continue processing and sending the message *if* we
-- don't flag it from _C:Filter.
return messageObj
end
And like that, you’ve also got a better player experience on top of blacklisting scam messages. Cool, right? I’ve only worked with OnClientFormattingMessage and OnCreatingChatWindow before, never OnServerReceivingMessage, so I learned a couple of things while typing this response up.
It’s my first time working with OSRM and in a support topic no less, so mistakes could be possible. Please do not hesitate to alert me if you run into a problem and I’ll try to help you to resolve it.
The Lua Chat System has great documentation but it lacks in some areas. That’s why I think of Developer Hub content requests to write about or in other cases, write Development Resources on it. I like to think that I’ve grabbed hold of a sufficient understanding of the LCS altogether. As a little plug, albeit off-topic, an example is sharing how you can change chat settings through RegisterChatCallback without forking anything.
A long response for a simple problem. Sorry for the long reading! I’m not too great with concise articulation and I also had a lot of material to go over for this. Hope you’ve learned something new along with being able to resolve your problem.
Cheers!