local m={}m.__index=m function m.AddChannel(c,m,o)if c.ChatChannels[m:lower()]then error(string.format("\067\104\097\110\110\101\108 \037\113 \097\108\114\097\100\121 \101\120\105\115\116\115\046",m))end local function h(o,h)if h:lower()=="\047\108\101\097\118\101"then local h=c:GetChannel(m)local n=c:GetSpeaker(o)if h and n then if h.Leavable then n:LeaveChannel(m)local o=ChatLocalization:FormatMessageToSend("\071\097\109\101\067\104\097\116\095\067\104\097\116\083\101\114\118\105\099\101\095\089\111\117\072\097\118\101\076\101\102\116\067\104\097\110\110\101\108",string.format("\089\111\117 \104\097\118\101 \108\101\102\116 \099\104\097\110\110\101\108 \'\037\115\'",m),"\082\066\088\095\078\065\077\069",m)n:SendSystemMessage(o,"\083\121\115\116\101\109")else n:SendSystemMessage(ChatLocalization:FormatMessageToSend("\071\097\109\101\067\104\097\116\095\067\104\097\116\083\101\114\118\105\099\101\095\067\097\110\110\111\116\076\101\097\118\101\067\104\097\110\110\101\108","\089\111\117 \099\097\110\110\111\116 \108\101\097\118\101 \116\104\105\115 \099\104\097\110\110\101\108\046"),m)end end return true end return false end local n=ChatChannel.new(c,m)c.ChatChannels[m:lower()]=n n:RegisterProcessCommandsFunction("\100\101\102\097\117\108\116\095\099\111\109\109\097\110\100\115",h,ChatConstants.HighPriority)local p,i=pcall(function()c.eChannelAdded:Fire(m)end)if not p and i then print("\069\114\114\111\114 \097\100\100\100\105\110\103 \099\104\097\110\110\101\108\058 "..i)end if o~=nil then n.AutoJoin=o if o then for o,c in pairs(c.Speakers)do c:JoinChannel(m)end end end return n end function m.RemoveChannel(o,m)if o.ChatChannels[m:lower()]then local c=o.ChatChannels[m:lower()].Name o.ChatChannels[m:lower()]:InternalDestroy()o.ChatChannels[m:lower()]=nil local h,n=pcall(function()o.eChannelRemoved:Fire(c)end)if not h and n then print("\069\114\114\111\114 \114\101\109\111\118\105\110\103 \099\104\097\110\110\101\108\058 "..n)end else warn(string.format("\067\104\097\110\110\101\108 \037\113 \100\111\101\115 \110\111\116 \101\120\105\115\116\046",m))end end function m.GetChannel(o,m)return o.ChatChannels[m:lower()]end function m.AddSpeaker(o,m)if o.Speakers[m:lower()]then error("\083\112\101\097\107\101\114 \""..(m.."\" \097\108\114\101\097\100\121 \101\120\105\115\116\115\033"))end local c=Speaker.new(o,m)o.Speakers[m:lower()]=c local h,n=pcall(function()o.eSpeakerAdded:Fire(m)end)if not h and n then print("\069\114\114\111\114 \097\100\100\105\110\103 \115\112\101\097\107\101\114\058 "..n)end return c end function m.InternalUnmuteSpeaker(o,m)for o,c in pairs(o.ChatChannels)do if c:IsSpeakerMuted(m)then c:UnmuteSpeaker(m)end end end function m.RemoveSpeaker(o,m)if o.Speakers[m:lower()]then local c=o.Speakers[m:lower()].Name o:InternalUnmuteSpeaker(c)o.Speakers[m:lower()]:InternalDestroy()o.Speakers[m:lower()]=nil local h,n=pcall(function()o.eSpeakerRemoved:Fire(c)end)if not h and n then print("\069\114\114\111\114 \114\101\109\111\118\105\110\103 \115\112\101\097\107\101\114\058 "..n)end else warn("\083\112\101\097\107\101\114 \""..(m.."\" \100\111\101\115 \110\111\116 \101\120\105\115\116\033"))end end function m.GetSpeaker(o,m)return o.Speakers[m:lower()]end function m.GetSpeakerByUserOrDisplayName(o,m)local c=o.Speakers[m:lower()]if c then return c end for o,c in pairs(o.Speakers)do local h=c:GetPlayer()if h and h.DisplayName:lower()==m:lower()then return c end end end function m.GetChannelList(m)local o={}for m,c in pairs(m.ChatChannels)do if not c.Private then table.insert(o,c.Name)end end return o end function m.GetAutoJoinChannelList(m)local o={}for m,c in pairs(m.ChatChannels)do if c.AutoJoin then table.insert(o,c)end end return o end function m.GetSpeakerList(m)local o={}for m,c in pairs(m.Speakers)do table.insert(o,c.Name)end return o end function m.SendGlobalSystemMessage(o,m)for o,c in pairs(o.Speakers)do c:SendSystemMessage(m,nil)end end function m.RegisterFilterMessageFunction(h,m,o,c)if h.FilterMessageFunctions:HasFunction(m)then error(string.format("\070\105\108\116\101\114\077\101\115\115\097\103\101\070\117\110\099\116\105\111\110 \'\037\115\' \097\108\114\101\097\100\121 \101\120\105\115\116\115",m))end h.FilterMessageFunctions:AddFunction(m,o,c)end function m.FilterMessageFunctionExists(o,m)return o.FilterMessageFunctions:HasFunction(m)end function m.UnregisterFilterMessageFunction(o,m)if not o.FilterMessageFunctions:HasFunction(m)then error(string.format("\070\105\108\116\101\114\077\101\115\115\097\103\101\070\117\110\099\116\105\111\110 \'\037\115\' \100\111\101\115 \110\111\116 \101\120\105\115\116\115",m))end o.FilterMessageFunctions:RemoveFunction(m)end function m.RegisterProcessCommandsFunction(h,m,o,c)if h.ProcessCommandsFunctions:HasFunction(m)then error(string.format("\080\114\111\099\101\115\115\067\111\109\109\097\110\100\115\070\117\110\099\116\105\111\110 \'\037\115\' \097\108\114\101\097\100\121 \101\120\105\115\116\115",m))end h.ProcessCommandsFunctions:AddFunction(m,o,c)end function m.ProcessCommandsFunctionExists(o,m)return o.ProcessCommandsFunctions:HasFunction(m)end function m.UnregisterProcessCommandsFunction(o,m)if not o.ProcessCommandsFunctions:HasFunction(m)then error(string.format("\080\114\111\099\101\115\115\067\111\109\109\097\110\100\115\070\117\110\099\116\105\111\110 \'\037\115\' \100\111\101\115 \110\111\116 \101\120\105\115\116",m))end o.ProcessCommandsFunctions:RemoveFunction(m)end local o=0 local c=0 local h=0 function m.InternalNotifyFilterIssue(m)if tick()-c>FILTER_THRESHOLD_TIME then h=0 end h=h+1 c=tick()if h>=FILTER_NOTIFCATION_THRESHOLD then if tick()-o>FILTER_NOTIFCATION_INTERVAL then o=tick()local c=m:GetChannel("\083\121\115\116\101\109")if c then c:SendSystemMessage(ChatLocalization:FormatMessageToSend("\071\097\109\101\067\104\097\116\095\067\104\097\116\083\101\114\118\105\099\101\095\067\104\097\116\070\105\108\116\101\114\073\115\115\117\101\115","\084\104\101 \099\104\097\116 \102\105\108\116\101\114 \105\115 \099\117\114\114\101\110\116\108\121 \101\120\112\101\114\105\101\110\099\105\110\103 \105\115\115\117\101\115 \097\110\100 \109\101\115\115\097\103\101\115 \109\097\121 \098\101 \115\108\111\119 \116\111 \097\112\112\101\097\114\046"),errorExtraData)end end end end local n={}
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods

function methods:AddChannel(channelName, autoJoin)
	if self.ChatChannels[channelName:lower()] then
		error(string.format("Channel %q alrady exists.", channelName))
	end

	local function DefaultChannelCommands(fromSpeaker, message)
		if message:lower() == "/leave" then
			local channel = self:GetChannel(channelName)
			local speaker = self:GetSpeaker(fromSpeaker)
			if channel and speaker then
				if channel.Leavable then
					speaker:LeaveChannel(channelName)
					local msg = ChatLocalization:FormatMessageToSend(
						"GameChat_ChatService_YouHaveLeftChannel",
						string.format("You have left channel '%s'", channelName),
						"RBX_NAME",
						channelName
					)
					speaker:SendSystemMessage(msg, "System")
				else
					speaker:SendSystemMessage(
						ChatLocalization:FormatMessageToSend(
							"GameChat_ChatService_CannotLeaveChannel",
							"You cannot leave this channel."
						),
						channelName
					)
				end
			end

			return true
		end
		return false
	end

	local channel = ChatChannel.new(self, channelName)
	self.ChatChannels[channelName:lower()] = channel

	channel:RegisterProcessCommandsFunction("default_commands", DefaultChannelCommands, ChatConstants.HighPriority)

	local success, err = pcall(function()
		self.eChannelAdded:Fire(channelName)
	end)
	if not success and err then
		print("Error addding channel: " .. err)
	end

	if autoJoin ~= nil then
		channel.AutoJoin = autoJoin
		if autoJoin then
			for _, speaker in pairs(self.Speakers) do
				speaker:JoinChannel(channelName)
			end
		end
	end

	return channel
end

function methods:RemoveChannel(channelName)
	if self.ChatChannels[channelName:lower()] then
		local n = self.ChatChannels[channelName:lower()].Name

		self.ChatChannels[channelName:lower()]:InternalDestroy()
		self.ChatChannels[channelName:lower()] = nil

		local success, err = pcall(function()
			self.eChannelRemoved:Fire(n)
		end)
		if not success and err then
			print("Error removing channel: " .. err)
		end
	else
		warn(string.format("Channel %q does not exist.", channelName))
	end
end

function methods:GetChannel(channelName)
	return self.ChatChannels[channelName:lower()]
end

function methods:AddSpeaker(speakerName)
	if self.Speakers[speakerName:lower()] then
		error('Speaker "' .. speakerName .. '" already exists!')
	end

	local speaker = Speaker.new(self, speakerName)
	self.Speakers[speakerName:lower()] = speaker

	local success, err = pcall(function()
		self.eSpeakerAdded:Fire(speakerName)
	end)
	if not success and err then
		print("Error adding speaker: " .. err)
	end

	return speaker
end

function methods:InternalUnmuteSpeaker(speakerName)
	for channelName, channel in pairs(self.ChatChannels) do
		if channel:IsSpeakerMuted(speakerName) then
			channel:UnmuteSpeaker(speakerName)
		end
	end
end

function methods:RemoveSpeaker(speakerName)
	if self.Speakers[speakerName:lower()] then
		local n = self.Speakers[speakerName:lower()].Name

		self:InternalUnmuteSpeaker(n)

		self.Speakers[speakerName:lower()]:InternalDestroy()
		self.Speakers[speakerName:lower()] = nil

		local success, err = pcall(function()
			self.eSpeakerRemoved:Fire(n)
		end)
		if not success and err then
			print("Error removing speaker: " .. err)
		end
	else
		warn('Speaker "' .. speakerName .. '" does not exist!')
	end
end

function methods:GetSpeaker(speakerName)
	return self.Speakers[speakerName:lower()]
end

function methods:GetSpeakerByUserOrDisplayName(speakerName)
	local speakerByUserName = self.Speakers[speakerName:lower()]

	if speakerByUserName then
		return speakerByUserName
	end

	for _, potentialSpeaker in pairs(self.Speakers) do
		local player = potentialSpeaker:GetPlayer()

		if player and player.DisplayName:lower() == speakerName:lower() then
			return potentialSpeaker
		end
	end
end

function methods:GetChannelList()
	local list = {}
	for i, channel in pairs(self.ChatChannels) do
		if not channel.Private then
			table.insert(list, channel.Name)
		end
	end
	return list
end

function methods:GetAutoJoinChannelList()
	local list = {}
	for i, channel in pairs(self.ChatChannels) do
		if channel.AutoJoin then
			table.insert(list, channel)
		end
	end
	return list
end

function methods:GetSpeakerList()
	local list = {}
	for i, speaker in pairs(self.Speakers) do
		table.insert(list, speaker.Name)
	end
	return list
end

function methods:SendGlobalSystemMessage(message)
	for i, speaker in pairs(self.Speakers) do
		speaker:SendSystemMessage(message, nil)
	end
end

function methods:RegisterFilterMessageFunction(funcId, func, priority)
	if self.FilterMessageFunctions:HasFunction(funcId) then
		error(string.format("FilterMessageFunction '%s' already exists", funcId))
	end
	self.FilterMessageFunctions:AddFunction(funcId, func, priority)
end

function methods:FilterMessageFunctionExists(funcId)
	return self.FilterMessageFunctions:HasFunction(funcId)
end

function methods:UnregisterFilterMessageFunction(funcId)
	if not self.FilterMessageFunctions:HasFunction(funcId) then
		error(string.format("FilterMessageFunction '%s' does not exists", funcId))
	end
	self.FilterMessageFunctions:RemoveFunction(funcId)
end

function methods:RegisterProcessCommandsFunction(funcId, func, priority)
	if self.ProcessCommandsFunctions:HasFunction(funcId) then
		error(string.format("ProcessCommandsFunction '%s' already exists", funcId))
	end
	self.ProcessCommandsFunctions:AddFunction(funcId, func, priority)
end

function methods:ProcessCommandsFunctionExists(funcId)
	return self.ProcessCommandsFunctions:HasFunction(funcId)
end

function methods:UnregisterProcessCommandsFunction(funcId)
	if not self.ProcessCommandsFunctions:HasFunction(funcId) then
		error(string.format("ProcessCommandsFunction '%s' does not exist", funcId))
	end
	self.ProcessCommandsFunctions:RemoveFunction(funcId)
end

local LastFilterNoficationTime = 0
local LastFilterIssueTime = 0
local FilterIssueCount = 0
function methods:InternalNotifyFilterIssue()
	if (tick() - LastFilterIssueTime) > FILTER_THRESHOLD_TIME then
		FilterIssueCount = 0
	end
	FilterIssueCount = FilterIssueCount + 1
	LastFilterIssueTime = tick()
	if FilterIssueCount >= FILTER_NOTIFCATION_THRESHOLD then
		if (tick() - LastFilterNoficationTime) > FILTER_NOTIFCATION_INTERVAL then
			LastFilterNoficationTime = tick()
			local systemChannel = self:GetChannel("System")
			if systemChannel then
				systemChannel:SendSystemMessage(
					ChatLocalization:FormatMessageToSend(
						"GameChat_ChatService_ChatFilterIssues",
						"The chat filter is currently experiencing issues and messages may be slow to appear."
					),
					errorExtraData
				)
			end
		end
	end
end

local StudioMessageFilteredCache = {}
