local L={}L.__index=L function L.SendSystemMessage(s,L,w)local F=s:InternalCreateMessageObject(L,nil,true,w)local m,H=pcall(function()s.eMessagePosted:Fire(F)end)if not m and H then print("\069\114\114\111\114 \112\111\115\116\105\110\103 \109\101\115\115\097\103\101\058 "..H)end s:InternalAddMessageToHistoryLog(F)for L,w in pairs(s.Speakers)do w:InternalSendSystemMessage(F,s.Name)end return F end function L.SendSystemMessageToSpeaker(F,L,w,s)local m=F.Speakers[w]if m then local w=F:InternalCreateMessageObject(L,nil,true,s)m:InternalSendSystemMessage(w,F.Name)elseif RunService:IsStudio()then 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",w,F.Name))end end function L.SendMessageObjToFilters(F,L,w,s)local m=w.Message w.Message=L F:InternalDoMessageFilter(s.Name,w,F.Name)F.ChatService:InternalDoMessageFilter(s.Name,w,F.Name)local H=w.Message w.Message=m return H end function L.CanCommunicateByUserId(s,L,w)if RunService:IsStudio()then return true end if L==0 or w==0 then return true end local F,m=pcall(function()return Chat:CanUsersChatAsync(L,w)end)return F and m end function L.CanCommunicate(s,L,w)local F=L:GetPlayer()local m=w:GetPlayer()if F and m then return s:CanCommunicateByUserId(F.UserId,m.UserId)end return true end function L.SendMessageToSpeaker(m,L,w,s,F)local H=m.Speakers[w]local g=m.ChatService:GetSpeaker(s)if H and g then local u=H:IsSpeakerMuted(s)if u then return end if not m:CanCommunicate(H,g)then return end local t=w==s local S=m:InternalCreateMessageObject(L,s,t,F)L=m:SendMessageObjToFilters(L,S,s)H:InternalSendMessage(S,m.Name)local N=m.Private and Enum.TextFilterContext.PrivateChat or Enum.TextFilterContext.PublicChat local y,f,C=m.ChatService:InternalApplyRobloxFilterNewAPI(S.FromSpeaker,L,N)if y then S.FilterResult=C S.IsFilterResult=f S.IsFiltered=true H:InternalSendFilteredMessageWithFilterResult(S,m.Name)end elseif RunService:IsStudio()then 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",w,m.Name))end end function L.KickSpeaker(s,L,w)local F=s.ChatService:GetSpeaker(L)if not F then error("\083\112\101\097\107\101\114 \""..(L.."\" \100\111\101\115 \110\111\116 \101\120\105\115\116\033"))end local m=""local H=""local g=F:GetNameForDisplay()if w then m=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",s.Name,w)H=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",g,w)else m=string.format("\089\111\117 \119\101\114\101 \107\105\099\107\101\100 \102\114\111\109 \'\037\115\'",s.Name)H=string.format("\037\115 \119\097\115 \107\105\099\107\101\100",g)end s:SendSystemMessageToSpeaker(m,L)F:LeaveChannel(s.Name)s:SendSystemMessage(H)end function L.MuteSpeaker(F,L,w,s)local m=F.ChatService:GetSpeaker(L)if not m then error("\083\112\101\097\107\101\114 \""..(L.."\" \100\111\101\115 \110\111\116 \101\120\105\115\116\033"))end F.Mutes[L:lower()]=(s==0 or s==nil)and 0 or os.time()+s if w then local L=m:GetNameForDisplay()F: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",L,w))end local H,g=pcall(function()F.eSpeakerMuted:Fire(L,w,s)end)if not H and g then print("\069\114\114\111\114 \109\117\116\116\105\110\103 \115\112\101\097\107\101\114\058 "..g)end local u=F.ChatService:GetSpeaker(L)if u then local L,m=pcall(function()u.eMuted:Fire(F.Name,w,s)end)if not L and m then print("\069\114\114\111\114 \109\117\116\116\105\110\103 \115\112\101\097\107\101\114\058 "..m)end end end function L.UnmuteSpeaker(w,L)local s=w.ChatService:GetSpeaker(L)if not s then error("\083\112\101\097\107\101\114 \""..(L.."\" \100\111\101\115 \110\111\116 \101\120\105\115\116\033"))end w.Mutes[L:lower()]=nil local F,m=pcall(function()w.eSpeakerUnmuted:Fire(L)end)if not F and m then print("\069\114\114\111\114 \117\110\109\117\116\105\110\103 \115\112\101\097\107\101\114\058 "..m)end local H=w.ChatService:GetSpeaker(L)if H then local L,s=pcall(function()H.eUnmuted:Fire(w.Name)end)if not L and s then print("\069\114\114\111\114 \117\110\109\117\116\105\110\103 \115\112\101\097\107\101\114\058 "..s)end end end function L.IsSpeakerMuted(w,L)return w.Mutes[L:lower()]~=nil end function L.GetSpeakerList(L)local w={}for L,s in pairs(L.Speakers)do table.insert(w,s.Name)end return w end function L.RegisterFilterMessageFunction(F,L,w,s)if F.FilterMessageFunctions:HasFunction(L)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",L))end F.FilterMessageFunctions:AddFunction(L,w,s)end function L.FilterMessageFunctionExists(w,L)return w.FilterMessageFunctions:HasFunction(L)end function L.UnregisterFilterMessageFunction(w,L)if not w.FilterMessageFunctions:HasFunction(L)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",L))end w.FilterMessageFunctions:RemoveFunction(L)end function L.RegisterProcessCommandsFunction(F,L,w,s)if F.ProcessCommandsFunctions:HasFunction(L)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",L))end F.ProcessCommandsFunctions:AddFunction(L,w,s)end function L.ProcessCommandsFunctionExists(w,L)return w.ProcessCommandsFunctions:HasFunction(L)end function L.UnregisterProcessCommandsFunction(w,L)if not w.ProcessCommandsFunctions:HasFunction(L)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",L))end w.ProcessCommandsFunctions:RemoveFunction(L)end local function w(L)local w={}for L,s in pairs(L)do w[L]=s end return w end function L.GetHistoryLog(L)return w(L.ChatHistory)end function L.GetHistoryLogForSpeaker(s,L)local F=-1 local m=L:GetPlayer()if m then F=m.UserId end local H={}for L=1,#s.ChatHistory,1 do local g=s.ChatHistory[L].SpeakerUserId if s:CanCommunicateByUserId(F,g)then local F=w(s.ChatHistory[L])if F.MessageType==ChatConstants.MessageTypeDefault or F.MessageType==ChatConstants.MessageTypeMeCommand then local L=F.FilterResult if F.IsFilterResult then if m then F.Message=L:GetChatForUserAsync(m.UserId)else F.Message=L:GetNonChatStringForBroadcastAsync()end else F.Message=L end end table.insert(H,F)end end return H 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)
	elseif RunService:IsStudio() then
		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)

		local textContext = self.Private and Enum.TextFilterContext.PrivateChat or Enum.TextFilterContext.PublicChat
		local filterSuccess, isFilterResult, filteredMessage = self.ChatService:InternalApplyRobloxFilterNewAPI(
			messageObj.FromSpeaker,
			message,
			textContext
		)
		if (filterSuccess) then
			messageObj.FilterResult = filteredMessage
			messageObj.IsFilterResult = isFilterResult
			messageObj.IsFiltered = true
			speakerTo:InternalSendFilteredMessageWithFilterResult(messageObj, self.Name)
		end
	elseif RunService:IsStudio() then
		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 = ""
	local playerName = speaker:GetNameForDisplay()

	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", playerName, reason)
	else
		messageToSpeaker = string.format("You were kicked from '%s'", self.Name)
		messageToChannel = string.format("%s was kicked", playerName)
	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
		local playerName = speaker:GetNameForDisplay()

		self:SendSystemMessage(string.format("%s was muted for the following reason(s): %s", playerName, 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 = {}

	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 or messageObj.MessageType == ChatConstants.MessageTypeMeCommand) 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
	return chatlog
end
