local E={}E.__index=E function E.SendSystemMessage(B,E,T)local u=B:InternalCreateMessageObject(E,nil,true,T)local v,S=pcall(function()B.eMessagePosted:Fire(u)end)if not v and S then print("\069\114\114\111\114 \112\111\115\116\105\110\103 \109\101\115\115\097\103\101\058 "..S)end B:InternalAddMessageToHistoryLog(u)for E,T in pairs(B.Speakers)do T:InternalSendSystemMessage(u,B.Name)end return u end function E.SendSystemMessageToSpeaker(u,E,T,B)local v=u.Speakers[T]if v then local T=u:InternalCreateMessageObject(E,nil,true,B)v:InternalSendSystemMessage(T,u.Name)else warn(string.format("\083\112\101\097\107\101\114 \'\037\115\' \105\115 \110\111\116 \105\110 \099\104\097\110\110\101\108 \'\037\115\' \097\110\100 \099\097\110\110\111\116 \098\101 \115\101\110\116 \097 \115\121\115\116\101\109 \109\101\115\115\097\103\101",T,u.Name))end end function E.SendMessageObjToFilters(u,E,T,B)local v=T.Message T.Message=E u:InternalDoMessageFilter(B.Name,T,u.Name)u.ChatService:InternalDoMessageFilter(B.Name,T,u.Name)local S=T.Message T.Message=v return S end function E.CanCommunicateByUserId(B,E,T)if RunService:IsStudio()then return true end if E==0 or T==0 then return true end local u,v=pcall(function()return Chat:CanUsersChatAsync(E,T)end)return u and v end function E.CanCommunicate(B,E,T)local u=E:GetPlayer()local v=T:GetPlayer()if u and v then return B:CanCommunicateByUserId(u.UserId,v.UserId)end return true end function E.SendMessageToSpeaker(v,E,T,B,u)local S=v.Speakers[T]local Z=v.ChatService:GetSpeaker(B)if S and Z then local R=S:IsSpeakerMuted(B)if R then return end if not v:CanCommunicate(S,Z)then return end local K=T==B local s=v:InternalCreateMessageObject(E,B,K,u)E=v:SendMessageObjToFilters(E,s,B)S:InternalSendMessage(s,v.Name)if not IN_GAME_CHAT_USE_NEW_FILTER_API then local B=v.ChatService:InternalApplyRobloxFilter(s.FromSpeaker,E,T)if B then s.Message=B s.IsFiltered=true S:InternalSendFilteredMessage(s,v.Name)end else local T,B,u=v.ChatService:InternalApplyRobloxFilterNewAPI(s.FromSpeaker,E)if T then s.FilterResult=u s.IsFilterResult=B s.IsFiltered=true S:InternalSendFilteredMessageWithFilterResult(s,v.Name)end end else warn(string.format("\083\112\101\097\107\101\114 \'\037\115\' \105\115 \110\111\116 \105\110 \099\104\097\110\110\101\108 \'\037\115\' \097\110\100 \099\097\110\110\111\116 \098\101 \115\101\110\116 \097 \109\101\115\115\097\103\101",T,v.Name))end end function E.KickSpeaker(B,E,T)local u=B.ChatService:GetSpeaker(E)if not u then error("\083\112\101\097\107\101\114 \""..(E.."\" \100\111\101\115 \110\111\116 \101\120\105\115\116\033"))end local v=""local S=""if T then v=string.format("\089\111\117 \119\101\114\101 \107\105\099\107\101\100 \102\114\111\109 \'\037\115\' \102\111\114 \116\104\101 \102\111\108\108\111\119\105\110\103 \114\101\097\115\111\110\040\115\041\058 \037\115",B.Name,T)S=string.format("\037\115 \119\097\115 \107\105\099\107\101\100 \102\111\114 \116\104\101 \102\111\108\108\111\119\105\110\103 \114\101\097\115\111\110\040\115\041\058 \037\115",E,T)else v=string.format("\089\111\117 \119\101\114\101 \107\105\099\107\101\100 \102\114\111\109 \'\037\115\'",B.Name)S=string.format("\037\115 \119\097\115 \107\105\099\107\101\100",E)end B:SendSystemMessageToSpeaker(v,E)u:LeaveChannel(B.Name)B:SendSystemMessage(S)end function E.MuteSpeaker(u,E,T,B)local v=u.ChatService:GetSpeaker(E)if not v then error("\083\112\101\097\107\101\114 \""..(E.."\" \100\111\101\115 \110\111\116 \101\120\105\115\116\033"))end u.Mutes[E:lower()]=(B==0 or B==nil)and 0 or os.time()+B if T then u:SendSystemMessage(string.format("\037\115 \119\097\115 \109\117\116\101\100 \102\111\114 \116\104\101 \102\111\108\108\111\119\105\110\103 \114\101\097\115\111\110\040\115\041\058 \037\115",E,T))end local S,Z=pcall(function()u.eSpeakerMuted:Fire(E,T,B)end)if not S and Z then print("\069\114\114\111\114 \109\117\116\116\105\110\103 \115\112\101\097\107\101\114\058 "..Z)end local R=u.ChatService:GetSpeaker(E)if R then local E,v=pcall(function()R.eMuted:Fire(u.Name,T,B)end)if not E and v then print("\069\114\114\111\114 \109\117\116\116\105\110\103 \115\112\101\097\107\101\114\058 "..v)end end end function E.UnmuteSpeaker(T,E)local B=T.ChatService:GetSpeaker(E)if not B then error("\083\112\101\097\107\101\114 \""..(E.."\" \100\111\101\115 \110\111\116 \101\120\105\115\116\033"))end T.Mutes[E:lower()]=nil local u,v=pcall(function()T.eSpeakerUnmuted:Fire(E)end)if not u and v then print("\069\114\114\111\114 \117\110\109\117\116\105\110\103 \115\112\101\097\107\101\114\058 "..v)end local S=T.ChatService:GetSpeaker(E)if S then local E,B=pcall(function()S.eUnmuted:Fire(T.Name)end)if not E and B then print("\069\114\114\111\114 \117\110\109\117\116\105\110\103 \115\112\101\097\107\101\114\058 "..B)end end end function E.IsSpeakerMuted(T,E)return T.Mutes[E:lower()]~=nil end function E.GetSpeakerList(E)local T={}for E,B in pairs(E.Speakers)do table.insert(T,B.Name)end return T end function E.RegisterFilterMessageFunction(u,E,T,B)if u.FilterMessageFunctions:HasFunction(E)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",E))end u.FilterMessageFunctions:AddFunction(E,T,B)end function E.FilterMessageFunctionExists(T,E)return T.FilterMessageFunctions:HasFunction(E)end function E.UnregisterFilterMessageFunction(T,E)if not T.FilterMessageFunctions:HasFunction(E)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",E))end T.FilterMessageFunctions:RemoveFunction(E)end function E.RegisterProcessCommandsFunction(u,E,T,B)if u.ProcessCommandsFunctions:HasFunction(E)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",E))end u.ProcessCommandsFunctions:AddFunction(E,T,B)end function E.ProcessCommandsFunctionExists(T,E)return T.ProcessCommandsFunctions:HasFunction(E)end function E.UnregisterProcessCommandsFunction(T,E)if not T.ProcessCommandsFunctions:HasFunction(E)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",E))end T.ProcessCommandsFunctions:RemoveFunction(E)end local function T(E)local T={}for E,B in pairs(E)do T[E]=B end return T end function E.GetHistoryLog(E)return T(E.ChatHistory)end function E.GetHistoryLogForSpeaker(B,E)local u=-1 local v=E:GetPlayer()if v then u=v.UserId end local S={}if not IN_GAME_CHAT_USE_NEW_FILTER_API then for E=1,#B.ChatHistory,1 do local v=B.ChatHistory[E].SpeakerUserId if B:CanCommunicateByUserId(u,v)then table.insert(S,T(B.ChatHistory[E]))end end else for E=1,#B.ChatHistory,1 do local Z=B.ChatHistory[E].SpeakerUserId if B:CanCommunicateByUserId(u,Z)then local u=T(B.ChatHistory[E])if u.MessageType==ChatConstants.MessageTypeDefault then local E=u.FilterResult if u.IsFilterResult then if v then u.Message=E:GetChatForUserAsync(v.UserId)else u.Message=E:GetNonChatStringForBroadcastAsync()end else u.Message=E end end table.insert(S,u)end end end return S end


local methods = {}
methods.__index = methods

function methods:SendSystemMessage(message, extraData)
	local messageObj = self:InternalCreateMessageObject(message, nil, true, extraData)

	local success, err = pcall(function() self.eMessagePosted:Fire(messageObj) end)
	if not success and err then
		print("Error posting message: " ..err)
	end

	self:InternalAddMessageToHistoryLog(messageObj)

	for i, speaker in pairs(self.Speakers) do
		speaker:InternalSendSystemMessage(messageObj, self.Name)
	end

	return messageObj
end

function methods:SendSystemMessageToSpeaker(message, speakerName, extraData)
	local speaker = self.Speakers[speakerName]
	if (speaker) then
		local messageObj = self:InternalCreateMessageObject(message, nil, true, extraData)
		speaker:InternalSendSystemMessage(messageObj, self.Name)
	else
		warn(string.format("Speaker '%s' is not in channel '%s' and cannot be sent a system message", speakerName, self.Name))
	end
end

function methods:SendMessageObjToFilters(message, messageObj, fromSpeaker)
	local oldMessage = messageObj.Message
	messageObj.Message = message
	self:InternalDoMessageFilter(fromSpeaker.Name, messageObj, self.Name)
	self.ChatService:InternalDoMessageFilter(fromSpeaker.Name, messageObj, self.Name)
	local newMessage = messageObj.Message
	messageObj.Message = oldMessage
	return newMessage
end

function methods:CanCommunicateByUserId(userId1, userId2)
	if RunService:IsStudio() then
		return true
	end
	-- UserId is set as 0 for non player speakers.
	if userId1 == 0 or userId2 == 0 then
		return true
	end
	local success, canCommunicate = pcall(function()
		return Chat:CanUsersChatAsync(userId1, userId2)
	end)
	return success and canCommunicate
end

function methods:CanCommunicate(speakerObj1, speakerObj2)
	local player1 = speakerObj1:GetPlayer()
	local player2 = speakerObj2:GetPlayer()
	if player1 and player2 then
		return self:CanCommunicateByUserId(player1.UserId, player2.UserId)
	end
	return true
end

function methods:SendMessageToSpeaker(message, speakerName, fromSpeakerName, extraData)
	local speakerTo = self.Speakers[speakerName]
	local speakerFrom = self.ChatService:GetSpeaker(fromSpeakerName)
	if speakerTo and speakerFrom then
		local isMuted = speakerTo:IsSpeakerMuted(fromSpeakerName)
		if isMuted then
			return
		end

		if not self:CanCommunicate(speakerTo, speakerFrom) then
			return
		end

		-- We need to claim the message is filtered even if it not in this case for compatibility with legacy client side code.
		local isFiltered = speakerName == fromSpeakerName
		local messageObj = self:InternalCreateMessageObject(message, fromSpeakerName, isFiltered, extraData)
		message = self:SendMessageObjToFilters(message, messageObj, fromSpeakerName)
		speakerTo:InternalSendMessage(messageObj, self.Name)

		--// START FFLAG
		if (not IN_GAME_CHAT_USE_NEW_FILTER_API) then --// USES FFLAG
		--// OLD BEHAVIOR
		local filteredMessage = self.ChatService:InternalApplyRobloxFilter(messageObj.FromSpeaker, message, speakerName)
		if filteredMessage then
			messageObj.Message = filteredMessage
			messageObj.IsFiltered = true
			speakerTo:InternalSendFilteredMessage(messageObj, self.Name)
		end
		--// OLD BEHAVIOR
		else
		--// NEW BEHAVIOR
			local filterSuccess, isFilterResult, filteredMessage = self.ChatService:InternalApplyRobloxFilterNewAPI(messageObj.FromSpeaker, message)
			if (filterSuccess) then
				messageObj.FilterResult = filteredMessage
				messageObj.IsFilterResult = isFilterResult
				messageObj.IsFiltered = true
				speakerTo:InternalSendFilteredMessageWithFilterResult(messageObj, self.Name)
			end
		--// NEW BEHAVIOR
		end
		--// END FFLAG
	else
		warn(string.format("Speaker '%s' is not in channel '%s' and cannot be sent a message", speakerName, self.Name))
	end
end

function methods:KickSpeaker(speakerName, reason)
	local speaker = self.ChatService:GetSpeaker(speakerName)
	if (not speaker) then
		error("Speaker \"" .. speakerName .. "\" does not exist!")
	end

	local messageToSpeaker = ""
	local messageToChannel = ""

	if (reason) then
		messageToSpeaker = string.format("You were kicked from '%s' for the following reason(s): %s", self.Name, reason)
		messageToChannel = string.format("%s was kicked for the following reason(s): %s", speakerName, reason)
	else
		messageToSpeaker = string.format("You were kicked from '%s'", self.Name)
		messageToChannel = string.format("%s was kicked", speakerName)
	end

	self:SendSystemMessageToSpeaker(messageToSpeaker, speakerName)
	speaker:LeaveChannel(self.Name)
	self:SendSystemMessage(messageToChannel)
end

function methods:MuteSpeaker(speakerName, reason, length)
	local speaker = self.ChatService:GetSpeaker(speakerName)
	if (not speaker) then
		error("Speaker \"" .. speakerName .. "\" does not exist!")
	end

	self.Mutes[speakerName:lower()] = (length == 0 or length == nil) and 0 or (os.time() + length)

	if (reason) then
		self:SendSystemMessage(string.format("%s was muted for the following reason(s): %s", speakerName, reason))
	end

	local success, err = pcall(function() self.eSpeakerMuted:Fire(speakerName, reason, length) end)
	if not success and err then
		print("Error mutting speaker: " ..err)
	end

	local spkr = self.ChatService:GetSpeaker(speakerName)
	if (spkr) then
		local success, err = pcall(function() spkr.eMuted:Fire(self.Name, reason, length) end)
		if not success and err then
			print("Error mutting speaker: " ..err)
		end
	end

end

function methods:UnmuteSpeaker(speakerName)
	local speaker = self.ChatService:GetSpeaker(speakerName)
	if (not speaker) then
		error("Speaker \"" .. speakerName .. "\" does not exist!")
	end

	self.Mutes[speakerName:lower()] = nil

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

	local spkr = self.ChatService:GetSpeaker(speakerName)
	if (spkr) then
		local success, err = pcall(function() spkr.eUnmuted:Fire(self.Name) end)
		if not success and err then
			print("Error unmuting speaker: " ..err)
		end
	end
end

function methods:IsSpeakerMuted(speakerName)
	return (self.Mutes[speakerName:lower()] ~= nil)
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: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 function ShallowCopy(table)
	local copy = {}
	for i, v in pairs(table) do
		copy[i] = v
	end
	return copy
end

function methods:GetHistoryLog()
	return ShallowCopy(self.ChatHistory)
end

function methods:GetHistoryLogForSpeaker(speaker)
	local userId = -1
	local player = speaker:GetPlayer()
	if player then
		userId = player.UserId
	end
	local chatlog = {}
	--// START FFLAG
	if (not IN_GAME_CHAT_USE_NEW_FILTER_API) then --// USES FFLAG
	--// OLD BEHAVIOR
	for i = 1, #self.ChatHistory do
		local logUserId = self.ChatHistory[i].SpeakerUserId
		if self:CanCommunicateByUserId(userId, logUserId) then
			table.insert(chatlog, ShallowCopy(self.ChatHistory[i]))
		end
	end
	--// OLD BEHAVIOR
	else
	--// NEW BEHAVIOR
		for i = 1, #self.ChatHistory do
			local logUserId = self.ChatHistory[i].SpeakerUserId
			if self:CanCommunicateByUserId(userId, logUserId) then
				local messageObj = ShallowCopy(self.ChatHistory[i])

				--// Since we're using the new filter API, we need to convert the stored filter result
				--// into an actual string message to send to players for their chat history.
				--// System messages aren't filtered the same way, so they just have a regular 
				--// text value in the Message field.
				if (messageObj.MessageType == ChatConstants.MessageTypeDefault) then
					local filterResult = messageObj.FilterResult
					if (messageObj.IsFilterResult) then
						if (player) then
							messageObj.Message = filterResult:GetChatForUserAsync(player.UserId)
						else
							messageObj.Message = filterResult:GetNonChatStringForBroadcastAsync()
						end
					else
						messageObj.Message = filterResult
					end
				end

				table.insert(chatlog, messageObj)
			end
		end
	--// NEW BEHAVIOR
	end
	--// END FFLAG
	return chatlog
end
