Potential Lua Chat System oversight: ChatMessage object given to MessagePosted does not include message

Issue Type: Other
Impact: Moderate
Frequency: Constantly
Date First Experienced:
Date Last Experienced: 2021-01-29 08:01:00 (-05:00)

Reproduction Steps:
This issue can be reproduced by simply printing the Message index of the ChatMessage object received by the MessagePosted signal. Below is the exact code I used to check for this. It should be in a regular standalone script in ServerScriptService.

local ServerScriptService = game:GetService("ServerScriptService")
local ChatService = require(ServerScriptService:WaitForChild("ChatServiceRunner").ChatService)

local function channelAdded(channelName)
	local channel = ChatService:GetChannel(channelName)
	
	channel.MessagePosted:Connect(function (messageObject)
		print(messageObject.Message)
	end)
end

ChatService.ChannelAdded:Connect(channelAdded)
for _, channel in ipairs(ChatService:GetChannelList()) do
	channelAdded(channel)
end

Expected Behavior:
When the repro steps are followed, my expectation is that the message that the player chatted would be sent to the console (except in the case of IsFiltered being true). For example, if I chat “foobar”, then “foobar” should be printed to the console.

Actual Behavior:
When the repro steps are followed, nil will be printed 100% of the time, meaning that the player’s chat is never included in the ChatMessage object that MessagePosted is fired with. This also happens regardless of the value of IsFiltered - the relevance being that the documentation states that ChatMessage.Message will be nil if IsFiltered is true.

Workaround:
ChatChannel’s InternalPostMessage method does a lot of manipulating with the message to put it through developer modifications (OnServerReceivingMessage chat callback and filter functions) as well as TextService filtering. However, after all processing is done, the new message (or even the raw) is never substituted back into the ChatMessage object.

My strategy to work around this issue is to fork the ChatServiceRunner script and substitute the processed message back into the ChatMessage right before MessagePosted is fired. This doesn’t feel like a clean workaround however especially with the need to fork and potential for some API members to receive a ChatMessage object with no Message index (as far as I know, only InternalSendMessage meets this criteria when this workaround is used), so officially addressing this problem would allow us to reliably use MessagePosted and access what the user chatted from it.

Workaround code:

messageObj.Message = message
local success, err = pcall(function() self.eMessagePosted:Fire(messageObj) end)

Line 411-412 Diff - ChatServiceRunner.ChatChannel

+ messageObj.Message = message
local success, err = pcall(function() self.eMessagePosted:Fire(messageObj) end)

Another workaround I have is to use ChatSpeaker.SaidMessage instead. Although this is much more reliable and removes the need to fork, it is also slightly more expensive. The expense comparison here is that instead of checking every time a new message is posted to the channel, I’m now checking every time a ChatSpeaker chats, channel regardless. The ChatMessage object sent to SaidMessage does include the chatted message, so I just need to check that the channelName parameter matches my target channel.

local ServerScriptService = game:GetService("ServerScriptService")
local ChatService = require(ServerScriptService:WaitForChild("ChatServiceRunner").ChatService)

local function speakerAdded(speakerName)
	local speaker = ChatService:GetSpeaker(speakerName)
	
	speaker.SaidMessage:Connect(function (messageObject, channelName)
		-- if not channelName == "foobar" then return end
		print(messageObject.Message)
	end)
end

ChatService.SpeakerAdded:Connect(speakerAdded)
for _, speaker in ipairs(ChatService:GetSpeakerList()) do
	speakerAdded(speaker)
end
5 Likes

Thanks for the report! We’ve filed a ticket to our internal database and we’ll follow up when we have an update for you.

3 Likes