Filtering Text - The Basics [EP 1]

This is episode one of a tutorial series I am creating! This is my first time doing something like this so please provide additional suggestions and feedback on this tutorial.

Fonts used:
Coolvetica
Cascadia Mono


Find the official Roblox Documentation for Text Filtering here

All important things are highlighed in blue. You can click blue text to find the official Documentation about something.


Chapter 1: Why & When?

Text filtering should always be used when user input is sent to other users. In easier words: You should filter text that a player has typed in. You shouldn’t filter things that the server is posting, such as any automatic posts, like timed chat messages and status messages. Only filter things uploaded by the client (later referred to as “Player” or/and “User”)! If you don’t filter things, you could be punished for letting users send/show/post inappropriate content, in most cases, curse words or phone numbers.

Chapter 2: How?

When I first tried to filter stuff, it came across as confusing, lot’s of functions and confusing stuff for a beginner. We’re gonna go over everything - and hopefully by the end you will understand most of it.

Let’s start with going over TextService, the home of text filtering (And getting textsize… but no one really cares about that…). ANYWAY! There isn’t much to talk about the actual service, it’s not all that interesting apart from that it does text filtering and moderating. Okay, let’s move on.

Let’s talk about FilterStringAsync, firstly lets learn what Async is, it’s short for asynchronous, meaning “controlling the timing of operations by the use of pulses sent when the previous operation is completed rather than at regular intervals.”, uhm, what? It basically just freezes a script until its finished. Read more confusing stuff about async here. Now, let’s get coding it!

FilterStringAsync takes 2 required arguments, and 1 optional argument, making 3 arguments! Woo basic maths!! Okay seriously now. These arguments go as the following:

stringToFilter: String
fromUserId: Integer
textContext: Enum

And this function, FilterStringAsync returns one object… an instance (TextFilterResult)!!
what?? a instance isn’t a string!! im not dumb?!?
Thanks captain obvious, Roblox is a bit picky so they want to know why it got filtered and who is filtering it… i dont really know why but i guess its for punishing people?

Alright then, let’s turn this instance into a string, it’s a bit more complex than it sounds, but in practise its really easy: We have 3 functions we can use on this instance:

GetChatForUserAsync
GetNonChatStringForBroadcastAsync
GetNonChatStringForUserAsync

All of these take the same or non arguments, so we don’t need to go over that. In this tutorial I’ll be using GetNonChatStringForUserAsync, which requires a UserID argument. The other two work the exact same as this, however the GetNonChatStringForBroadcastAsync doesn’t require a UserId.

When do I use GetChatForUserAsync?

Taken from this documentation

The GetChatForUserAsync function returns the text in a properly filtered manner for the specified Player.UserId . This should be used in the context of chats between players, although there are some other cases where text filtering is required.

This function returns the string appropriate for sending and displaying to a target user (specified by toUserId ) from the original sender using the least restrictive filtering appropriate for the target user, with Chat privacy settings of both users enforced. This string should only be shown to the target user, as it might not be appropriate for all users.

This method throws an error if the two users are not allowed to chat (that is, if Chat:CanUserChatAsync would return false for the given sender and receiver). If this method throws the string should not be displayed to the user. In addition, this function will throw an error if CanUserChatAsync would return false, so CanUserChatAsync should be called first to check.

This function currently throws an error if the user with the id toUserId is not online on the current server.

If text can be used for real-time or near real-time communication it should use this method.

This function will return immediately in most cases. The only time it will yield is if the target user is offline or has just joined the server and their filtering info is not yet loaded.

When do I use GetNonChatStringForBroadcastAsync?

Returns the text in a properly filtered manner for all users: Basically use it whenever something is being shown to all players.

When do I use GetNonChatStringForUserAsync?

Returns the text in a properly filtered manner for the specified Player.UserId . This should be used in the context of non-chat text that another user can see, such as the name of a pet.

Cool, now you know which one to use, hopefully. Let’s show some example code to get you going.

Here’s an example function to filter some text for a player:

local function filterAsync(text, player)
	local filteredTextResult
	local success, errorMessage = pcall(function()
		filteredTextResult = TextService:FilterStringAsync(text, player.UserId)
	end)
	
	if not success then
		warn("Error filtering text:", text, ":", errorMessage)
		return false, "Error while filtering text"
	end
	
	repeat task.wait() until filteredTextResult
	
	return true, filteredTextResult:GetNonChatStringForUserAsync(player.UserId)
end

Now we shall go through significant each section., describing what it does.

Section 1

	local filteredTextResult
	local success, errorMessage = pcall(function()
		filteredTextResult = TextService:FilterStringAsync(text, player.UserId)
	end)

First we define filteredTextResult as nothing, so we can set it later on, in our case, the next line. We then need to run the FilterStringAsync function we mentioned earlier, to get our TextFilterResult!. We need to wrap this in a pcall, because TextService:FilterStringAsync makes a webrequest, and sometimes they fail (dumb servers!!), so incase it fails, the pcall will protect it and not break everything. Like a shield! :shield:

Section 2

	if not success then
		warn("Error filtering text:", text, ":", errorMessage)
		return false, "Error while filtering text"
	end

The first line makes grammatical sense, if not success, then… basically if it fails then we’ll run the next piece of code. The next code basically just sends a output warning saying that it failed, along with some error information. Then we return false and the error so we know what happened later.

Section 3

	repeat task.wait() until filteredTextResult
	
	return true, filteredTextResult:GetNonChatStringForUserAsync(player.UserId)

The first line might not be english to you, but it simply waits until Roblox has given us the result, in our case the filteredTextResult.

Then finally we return true meaning that it worked, along with the actual string (text), which we get by running filteredTextResult:GetNonChatStringForUserAsync(player.UserId).

Section 4 | Extra

Okay, we’ve got the function right, now lets use the function. Probably the easiest part.
First call it with 2 variables, result and str.

Let’s test it with some… bad words.

filterAsync("Hey, my phone number is 14581-457180-557271!", RudeBob)

RudeBob is breaking ToS! Let’s hope our function has stopped RudeBob from exposing such information.

image

Haha! Take that, RudeBob!

Disclaimer: RudeBob isn’t real, and you will need to specify an actual player object for this function.


Chapter 3: The End

Thanks for reading all the way! I hope you learned a thing or two. This is my first ever long tutorial, so I hope I explained things well. I will be making more tutorials in the future I hope!

If you have any criticism, feedback, or suggestions, please tell me!


Extras:

All Code
local function filterAsync(text, player)
	local filteredTextResult
	local success, errorMessage = pcall(function()
		filteredTextResult = TextService:FilterStringAsync(text, player.UserId)
	end)
	
	if not success then
		warn("Error filtering text:", text, ":", errorMessage)
		return false, "Error while filtering text"
	end
	
	repeat task.wait() until filteredTextResult
	
	return true, filteredTextResult:GetNonChatStringForUserAsync(player.UserId)
end

--
local Core = {}

function Core:FilterStringAsync(player, str)
	return filterAsync(str, player)
end

return Core
Test Code Used
print(require(game:GetService("ServerStorage").Core):FilterStringAsync(game.Players:WaitForChild("fvazer"), "Hey, my phone number is 14581-457180-557271 "))
13 Likes

little poll for my own use :slight_smile:

  • This was easy to follow
  • This was okay to follow
  • This was difficult to follow

0 voters

Your Rating
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

0 voters

1 Like

It’s nearly time for the next tutorial! What would you like a tutorial about?

Reply to this post with what you want to see a detailed tutorial about.

Yes he is

https://www.roblox.com/users/16427720/profile

3 Likes

I guess i’m slandering RudeBob now

2 Likes

DataStores would be a nice tutorial

2 Likes

Oh and RudeJimmy will work, they are not real

What are these accounts? I’ve seen accounts like this. No friends just 1 following and it’s builderman, another acc was like that too.

A few years ago builderman used to follow all new accounts. This has now stopped for obvious reasons.
image

These old accounts were probably just accounts used years ago and haven’t been touched since.
image

No I meant the following not followers in the account s. Wierd

Oh, I’m 99% sure it made you automatically follow builderman too.

Is there a way to detect when the filter passed/failed the player’s input? I am making a system where the player can rename his or her base, and I’d like to have a feature where if the player’s input failed to pass the filter, the player gets notified of it, rather than the player doing trial-and-error to see what input will go through - we all know ROBLOX’s filtering isn’t the best…

For this, I could simply do something that looks for hashtags after the filter has checked the input, but what about when a player intentionally puts hashtags in the input? Think of something where a player would name their base to be something like “#TaylorGang HQ”