roblox-text-chat

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Chat and text safety

聊天与文本安全

Non-negotiable rules (Roblox Community Standards)

不可违反的规则(Roblox 社区标准)

  1. Player-to-player conversation must go through TextChatService
    TextChannel
    s (handles filtering, privacy/parental settings, and abuse reporting). The legacy
    Chat
    service/
    ChatService
    Lua modules are gone —
    TextChatService
    is the only chat system.
  2. Any text authored by one player and shown to another (pet names, signs, guild names, trade notes, custom UI) must be filtered with
    TextService:FilterStringAsync
    on the server before display — and re-filtered when loaded from a DataStore later.
  3. Editable text others can see (signs, bulletin boards) needs a rate limit of at least 1 minute.
  4. Respect privacy settings with
    CanUserChatAsync
    /
    CanUsersChatAsync
    /
    CanUsersDirectChatAsync
    and mark direct-message channels with
    TextChannel:SetDirectChatRequester()
    .
Not chat (no filtering needed): your own UI text, game status messages, admin announcements you write.
  1. 玩家之间的对话必须通过 TextChatService 的
    TextChannel
    (负责过滤、隐私/家长设置和滥用举报)。旧版
    Chat
    服务/
    ChatService
    Lua 模块已被移除——
    TextChatService
    是唯一的聊天系统。
  2. 任何由一名玩家创作并向另一名玩家展示的文本(宠物名称、告示牌、公会名称、交易备注、自定义 UI),都必须在服务器上使用
    TextService:FilterStringAsync
    进行过滤——以后从 DataStore 加载时也必须重新过滤。
  3. 可编辑且他人可见的文本(告示牌、公告板)需要至少 1 分钟的速率限制。
  4. 遵守隐私设置,使用
    CanUserChatAsync
    /
    CanUsersChatAsync
    /
    CanUsersDirectChatAsync
    ,并通过
    TextChannel:SetDirectChatRequester()
    标记私信频道。
不属于聊天的内容(无需过滤):你自己的 UI 文本、游戏状态消息、你编写的管理员公告。

Filtering player-authored text

过滤玩家创作的文本

luau
--!strict
local TextService = game:GetService("TextService")

-- Broadcast (e.g. a sign everyone sees). Returns nil if filtering failed: then show nothing.
local function filterForEveryone(text: string, fromUserId: number): string?
	local ok, result = pcall(function()
		local filterResult = TextService:FilterStringAsync(text, fromUserId, Enum.TextFilterContext.PublicChat)
		return filterResult:GetNonChatStringForBroadcastAsync()
	end)
	return if ok then result else nil
end

-- Per-recipient (respects each viewer's age/settings; e.g. a private note).
local function filterForUser(text: string, fromUserId: number, toUserId: number): string?
	local ok, result = pcall(function()
		local filterResult = TextService:FilterStringAsync(text, fromUserId, Enum.TextFilterContext.PrivateChat)
		return filterResult:GetNonChatStringForUserAsync(toUserId)
	end)
	return if ok then result else nil
end

return { forEveryone = filterForEveryone, forUser = filterForUser }
  • Filter on the server, after the player submits (not per keystroke); rate-limit and length-cap first.
  • On failure, fail closed (show nothing / a placeholder), never the raw text.
  • Store the raw text if you must, but always filter at display time (filters and viewer settings change).
  • Numbers-only or short strings can still be filtered (hashtags
    ###
    ); design UI for that.
luau
--!strict
local TextService = game:GetService("TextService")

-- Broadcast (e.g. a sign everyone sees). Returns nil if filtering failed: then show nothing.
local function filterForEveryone(text: string, fromUserId: number): string?
	local ok, result = pcall(function()
		local filterResult = TextService:FilterStringAsync(text, fromUserId, Enum.TextFilterContext.PublicChat)
		return filterResult:GetNonChatStringForBroadcastAsync()
	end)
	return if ok then result else nil
end

-- Per-recipient (respects each viewer's age/settings; e.g. a private note).
local function filterForUser(text: string, fromUserId: number, toUserId: number): string?
	local ok, result = pcall(function()
		local filterResult = TextService:FilterStringAsync(text, fromUserId, Enum.TextFilterContext.PrivateChat)
		return filterResult:GetNonChatStringForUserAsync(toUserId)
	end)
	return if ok then result else nil
end

return { forEveryone = filterForEveryone, forUser = filterForUser }
  • 在服务器上、玩家提交后进行过滤(而不是每次按键时过滤);先实施速率限制并限制长度。
  • 过滤失败时,必须采用失败关闭策略(不显示任何内容/显示占位符),绝不显示原始文本。
  • 如果确有需要,可以存储原始文本,但始终必须在显示时进行过滤(过滤规则和观看者设置可能发生变化)。
  • 纯数字或短字符串仍可能被过滤(井号
    ###
    );应据此设计 UI。

TextChatService essentials

TextChatService 基础知识

  • Defaults:
    CreateDefaultTextChannels
    creates
    RBXGeneral
    and
    RBXSystem
    ;
    CreateDefaultCommands
    adds built-ins (
    /whisper
    ,
    /team
    ,
    /mute
    , ...). Configure look via
    ChatWindowConfiguration
    ,
    ChatInputBarConfiguration
    ,
    BubbleChatConfiguration
    (or disable and build your own UI with
    TextChannel:SendAsync
    and
    TextChatService.MessageReceived
    ).
  • Flow: client
    TextChannel:SendAsync(text)
    → server
    TextChannel.ShouldDeliverCallback(message, textSource)
    per recipient → filtering → clients'
    OnIncomingMessage
    callbacks → display.
  • Callbacks must not yield.
    ShouldDeliverCallback
    is server-only;
    TextChatService.OnIncomingMessage
    and
    TextChannel.OnIncomingMessage
    are client-only (formatting).
  • System messages:
    channel:DisplaySystemMessage(text)
    on the client (local only).
  • Custom channels (team, party, proximity): create
    TextChannel
    s under
    TextChatService
    on the server,
    channel:AddUserAsync(userId)
    to add members; check the returned
    TextSource
    's
    CanSend
    .
luau
--!strict
-- Client: chat tags and colors via OnIncomingMessage (formatting only; never yields).
local Players = game:GetService("Players")
local TextChatService = game:GetService("TextChatService")

TextChatService.OnIncomingMessage = function(message: TextChatMessage)
	local properties = Instance.new("TextChatMessageProperties")
	local source = message.TextSource
	if source then
		local player = Players:GetPlayerByUserId(source.UserId)
		if player and player:GetAttribute("VIP") then
			properties.PrefixText = `<font color="#F5CD30">[VIP]</font> {message.PrefixText}`
		end
	end
	return properties
end
luau
--!strict
-- Server: a /spawn command, restricted to admins.
local Players = game:GetService("Players")
local TextChatService = game:GetService("TextChatService")

local ADMINS: { [number]: boolean } = { [123456] = true }

local command = Instance.new("TextChatCommand")
command.Name = "SpawnCommand"
command.PrimaryAlias = "/spawn"
command.Parent = TextChatService

command.Triggered:Connect(function(source: TextSource, unfilteredText: string)
	if not ADMINS[source.UserId] then
		return
	end
	local player = Players:GetPlayerByUserId(source.UserId)
	print(`{player and player.Name} ran: {unfilteredText}`)
end)
More: proximity chat, team channels, custom UI, and migrating from legacy chat: references/chat-recipes.md.
  • 默认设置:
    CreateDefaultTextChannels
    会创建
    RBXGeneral
    和
    RBXSystem
    ;
    CreateDefaultCommands
    会添加内置命令(
    /whisper
    、
    /team
    、
    /mute
    等)。可通过
    ChatWindowConfiguration
    、
    ChatInputBarConfiguration
    、
    BubbleChatConfiguration
    配置外观(也可以禁用这些功能,并使用
    TextChannel:SendAsync
    和
    TextChatService.MessageReceived
    构建自己的 UI)。
  • 流程:客户端
    TextChannel:SendAsync(text)
    → 服务器针对每位接收者执行
    TextChannel.ShouldDeliverCallback(message, textSource)
    → 过滤 → 客户端的
    OnIncomingMessage
    回调 → 显示。
  • 回调不得 yield。
    ShouldDeliverCallback
    仅限服务器使用;
    TextChatService.OnIncomingMessage
    和
    TextChannel.OnIncomingMessage
    仅限客户端使用(用于格式化)。
  • 系统消息:在客户端调用
    channel:DisplaySystemMessage(text)
    (仅在本地显示)。
  • 自定义频道(团队、队伍、附近聊天):在服务器上于
    TextChatService
    下创建
    TextChannel
    ,通过
    channel:AddUserAsync(userId)
    添加成员;检查返回的
    TextSource
    的
    CanSend
    。
luau
--!strict
-- Client: chat tags and colors via OnIncomingMessage (formatting only; never yields).
local Players = game:GetService("Players")
local TextChatService = game:GetService("TextChatService")

TextChatService.OnIncomingMessage = function(message: TextChatMessage)
	local properties = Instance.new("TextChatMessageProperties")
	local source = message.TextSource
	if source then
		local player = Players:GetPlayerByUserId(source.UserId)
		if player and player:GetAttribute("VIP") then
			properties.PrefixText = `<font color="#F5CD30">[VIP]</font> {message.PrefixText}`
		end
	end
	return properties
end
luau
--!strict
-- Server: a /spawn command, restricted to admins.
local Players = game:GetService("Players")
local TextChatService = game:GetService("TextChatService")

local ADMINS: { [number]: boolean } = { [123456] = true }

local command = Instance.new("TextChatCommand")
command.Name = "SpawnCommand"
command.PrimaryAlias = "/spawn"
command.Parent = TextChatService

command.Triggered:Connect(function(source: TextSource, unfilteredText: string)
	if not ADMINS[source.UserId] then
		return
	end
	local player = Players:GetPlayerByUserId(source.UserId)
	print(`{player and player.Name} ran: {unfilteredText}`)
end)
更多内容:附近聊天、团队频道、自定义 UI,以及从旧版聊天迁移: references/chat-recipes.md。

PolicyService (per-user restrictions)

PolicyService(按用户限制)

PolicyService:GetPolicyInfoForPlayerAsync(player)
(server,
pcall
) returns flags such as
ArePaidRandomItemsRestricted
,
IsPaidItemTradingAllowed
,
AreAdsAllowed
,
AllowedExternalLinkReferences
,
IsSubjectToChinaPolicies
,
IsContentSharingAllowed
. Gate loot boxes, trading, ads, and links to social media (only show platforms listed in
AllowedExternalLinkReferences
) per player.
PolicyService:GetPolicyInfoForPlayerAsync(player)
(服务器,使用
pcall
)会返回以下标志,例如
ArePaidRandomItemsRestricted
、
IsPaidItemTradingAllowed
、
AreAdsAllowed
、
AllowedExternalLinkReferences
、
IsSubjectToChinaPolicies
、
IsContentSharingAllowed
。应根据每位玩家的这些标志,对战利品箱、交易、广告和社交媒体链接实施限制(仅显示
AllowedExternalLinkReferences
中列出的平台)。

Related skills

相关 skill

roblox-security
(rate limits, validation),
roblox-ui
(text input UI),
roblox-monetization
(paid random items),
roblox-data-stores
(storing user text).
roblox-security
(速率限制、验证)、
roblox-ui
(文本输入 UI)、
roblox-monetization
(付费随机物品)、
roblox-data-stores
(存储用户文本)。