function methods.InternalDestroy(E)for R,U in pairs(E.Speakers)do U:LeaveChannel(E.Name)end E.eDestroyed:Fire()E.eDestroyed:Destroy()E.eMessagePosted:Destroy()E.eSpeakerJoined:Destroy()E.eSpeakerLeft:Destroy()E.eSpeakerMuted:Destroy()E.eSpeakerUnmuted:Destroy()end function methods.InternalDoMessageFilter(A,E,R,U)local J=A.FilterMessageFunctions:GetIterator()for A,J,g in J do local q,a=pcall(function()J(E,R,U)end)if not q then warn(string.format("\068\111\077\101\115\115\097\103\101\070\105\108\116\101\114 \070\117\110\099\116\105\111\110 \'\037\115\' \102\097\105\108\101\100 \102\111\114 \114\101\097\115\111\110\058 \037\115",A,a))end end end function methods.InternalDoProcessCommands(A,E,R,U)local J=A.ProcessCommandsFunctions:GetIterator()for A,J,g in J do local q,a=pcall(function()local A=J(E,R,U)if type(A)~="\098\111\111\108\101\097\110"then error("\080\114\111\099\101\115\115 \099\111\109\109\097\110\100 \102\117\110\099\116\105\111\110\115 \109\117\115\116 \114\101\116\117\114\110 \097 \098\111\111\108")end return A end)if not q then warn(string.format("\068\111\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\' \102\097\105\108\101\100 \102\111\114 \114\101\097\115\111\110\058 \037\115",A,a))elseif a then return true end end return false end function methods.InternalPostMessage(A,E,R,U)if A:InternalDoProcessCommands(E.Name,R,A.Name)then return false end if A.Mutes[E.Name:lower()]~=nil then local R=A.Mutes[E.Name:lower()]if R>0 and os.time()>R then A:UnmuteSpeaker(E.Name)else A:SendSystemMessageToSpeaker(ChatLocalization:FormatMessageToSend("\071\097\109\101\067\104\097\116\095\067\104\097\116\067\104\097\110\110\101\108\095\077\117\116\101\100\073\110\067\104\097\110\110\101\108","\089\111\117 \097\114\101 \109\117\116\101\100 \097\110\100 \099\097\110\110\111\116 \116\097\108\107 \105\110 \116\104\105\115 \099\104\097\110\110\101\108"),E.Name)return false end end local J=A:InternalCreateMessageObject(R,E.Name,false,U)J.Message=R local g pcall(function()g=Chat:InvokeChatCallback(Enum.ChatCallbackType.OnServerReceivingMessage,J)end)J.Message=nil if g then if g.ShouldDeliver==false then return false end J=g end R=A:SendMessageObjToFilters(R,J,E)local q={}for U,g in pairs(A.Speakers)do local a=g:IsSpeakerMuted(E.Name)if not a and A:CanCommunicate(E,g)then table.insert(q,g.Name)if g.Name==E.Name then local E=ShallowCopy(J)if userShouldMuteUnfilteredMessage then local R=J.MessageLengthUtf8 or J.MessageLength E.Message=string.rep("\095",R)else E.Message=R end E.IsFiltered=true g:InternalSendMessage(E,A.Name)else g:InternalSendMessage(J,A.Name)end end end local a,l=pcall(function()A.eMessagePosted:Fire(J)end)if not a and l then print("\069\114\114\111\114 \112\111\115\116\105\110\103 \109\101\115\115\097\103\101\058 "..l)end local T=A.Private and Enum.TextFilterContext.PrivateChat or Enum.TextFilterContext.PublicChat local w,x,D=A.ChatService:InternalApplyRobloxFilterNewAPI(J.FromSpeaker,R,T)if w then J.FilterResult=D J.IsFilterResult=x else if UserFlagRemoveMessageOnTextFilterFailures then J.IsFilterResult=false J.FilterResult=""J.MessageLength=0 else return false end end J.IsFiltered=true A:InternalAddMessageToHistoryLog(J)for E,R in pairs(q)do local U=A.Speakers[R]if U then U:InternalSendFilteredMessageWithFilterResult(J,A.Name)end end local S={}for R,U in pairs(A.Speakers)do local J=U:IsSpeakerMuted(E.Name)if not J and A:CanCommunicate(E,U)then local E=false for R,A in pairs(q)do if U.Name==A then E=true break end end if not E then table.insert(S,U.Name)end end end for E,R in pairs(S)do local U=A.Speakers[R]if U then U:InternalSendFilteredMessageWithFilterResult(J,A.Name)end end return J end function methods.InternalAddSpeaker(R,E)if R.Speakers[E.Name]then warn("\083\112\101\097\107\101\114 \""..(E.name.."\" \105\115 \097\108\114\101\097\100\121 \105\110 \116\104\101 \099\104\097\110\110\101\108\033"))return end R.Speakers[E.Name]=E local U,A=pcall(function()R.eSpeakerJoined:Fire(E.Name)end)if not U and A then print("\069\114\114\111\114 \114\101\109\111\118\105\110\103 \099\104\097\110\110\101\108\058 "..A)end end function methods.InternalRemoveSpeaker(R,E)if not R.Speakers[E.Name]then warn("\083\112\101\097\107\101\114 \""..(E.name.."\" \105\115 \110\111\116 \105\110 \116\104\101 \099\104\097\110\110\101\108\033"))return end R.Speakers[E.Name]=nil local U,A=pcall(function()R.eSpeakerLeft:Fire(E.Name)end)if not U and A then print("\069\114\114\111\114 \114\101\109\111\118\105\110\103 \115\112\101\097\107\101\114\058 "..A)end end function methods.InternalRemoveExcessMessagesFromLog(E)local R=table.remove while#E.ChatHistory>E.MaxHistory do R(E.ChatHistory,1)end end function methods.InternalAddMessageToHistoryLog(R,E)table.insert(R.ChatHistory,E)R:InternalRemoveExcessMessagesFromLog()end function methods.GetMessageType(U,E,R)if R==nil then return ChatConstants.MessageTypeSystem end return ChatConstants.MessageTypeDefault end function methods.InternalCreateMessageObject(J,E,R,U,A)local g=J:GetMessageType(E,R)local q=-1 local a=nil local l=nil if R then l=J.ChatService:GetSpeaker(R)if l then local E=l:GetPlayer()if E then q=E.UserId if ChatSettings.PlayerDisplayNamesEnabled then a=l:GetNameForDisplay()end else q=0 end end end local T={ID=J.ChatService:InternalGetUniqueMessageId();FromSpeaker=R,SpeakerDisplayName=a,SpeakerUserId=q;OriginalChannel=J.Name,MessageLength=string.len(E),MessageLengthUtf8=utf8.len(utf8.nfcnormalize(E)),MessageType=g;IsFiltered=U,Message=U and E or nil;Time=os.time(),ExtraData={}}if l then for E,R in pairs(l.ExtraData)do T.ExtraData[E]=R end end if A then for E,R in pairs(A)do T.ExtraData[E]=R end end return T end function methods.SetChannelNameColor(R,E)R.ChannelNameColor=E for U,A in pairs(R.Speakers)do A:UpdateChannelNameColor(R.Name,E)end end function methods.GetWelcomeMessageForSpeaker(R,E)if R.GetWelcomeMessageFunction then local U=R.GetWelcomeMessageFunction(E)if U then return U end end return R.WelcomeMessage end function methods.RegisterGetWelcomeMessageFunction(R,E)if type(E)~="\102\117\110\099\116\105\111\110"then error("\082\101\103\105\115\116\101\114\071\101\116\087\101\108\099\111\109\101\077\101\115\115\097\103\101\070\117\110\099\116\105\111\110 \109\117\115\116 \098\101 \099\097\108\108\101\100 \119\105\116\104 \097 \102\117\110\099\116\105\111\110\046")end R.GetWelcomeMessageFunction=E end function methods.UnRegisterGetWelcomeMessageFunction(E)E.GetWelcomeMessageFunction=nil end

function methods:InternalDestroy()
	for i, speaker in pairs(self.Speakers) do
		speaker:LeaveChannel(self.Name)
	end

	self.eDestroyed:Fire()

	self.eDestroyed:Destroy()
	self.eMessagePosted:Destroy()
	self.eSpeakerJoined:Destroy()
	self.eSpeakerLeft:Destroy()
	self.eSpeakerMuted:Destroy()
	self.eSpeakerUnmuted:Destroy()
end

function methods:InternalDoMessageFilter(speakerName, messageObj, channel)
	local filtersIterator = self.FilterMessageFunctions:GetIterator()
	for funcId, func, priority in filtersIterator do
		local success, errorMessage = pcall(function()
			func(speakerName, messageObj, channel)
		end)

		if not success then
			warn(string.format("DoMessageFilter Function '%s' failed for reason: %s", funcId, errorMessage))
		end
	end
end

function methods:InternalDoProcessCommands(speakerName, message, channel)
	local commandsIterator = self.ProcessCommandsFunctions:GetIterator()
	for funcId, func, priority in commandsIterator do
		local success, returnValue = pcall(function()
			local ret = func(speakerName, message, channel)
			if type(ret) ~= "boolean" then
				error("Process command functions must return a bool")
			end
			return ret
		end)

		if not success then
			warn(string.format("DoProcessCommands Function '%s' failed for reason: %s", funcId, returnValue))
		elseif returnValue then
			return true
		end
	end

	return false
end

function methods:InternalPostMessage(fromSpeaker, message, extraData)
	if (self:InternalDoProcessCommands(fromSpeaker.Name, message, self.Name)) then return false end

	if (self.Mutes[fromSpeaker.Name:lower()] ~= nil) then
		local t = self.Mutes[fromSpeaker.Name:lower()]
		if (t > 0 and os.time() > t) then
			self:UnmuteSpeaker(fromSpeaker.Name)
		else
			self:SendSystemMessageToSpeaker(ChatLocalization:FormatMessageToSend("GameChat_ChatChannel_MutedInChannel","You are muted and cannot talk in this channel"), fromSpeaker.Name)
			return false
		end
	end

	local messageObj = self:InternalCreateMessageObject(message, fromSpeaker.Name, false, extraData)

	-- allow server to process the unfiltered message string
	messageObj.Message = message
	local processedMessage
	pcall(function()
		processedMessage = Chat:InvokeChatCallback(Enum.ChatCallbackType.OnServerReceivingMessage, messageObj)
	end)
	messageObj.Message = nil

	if processedMessage then

		-- developer server code's choice to mute the message
		if processedMessage.ShouldDeliver == false then
			return false
		end
		messageObj = processedMessage
	end

	message = self:SendMessageObjToFilters(message, messageObj, fromSpeaker)

	local sentToList = {}
	for i, speaker in pairs(self.Speakers) do
		local isMuted = speaker:IsSpeakerMuted(fromSpeaker.Name)
		if not isMuted and self:CanCommunicate(fromSpeaker, speaker) then
			table.insert(sentToList, speaker.Name)
			if speaker.Name == fromSpeaker.Name then
				-- Send unfiltered message to speaker who sent the message.
				local cMessageObj = ShallowCopy(messageObj)
				if userShouldMuteUnfilteredMessage then
					local messageLength = messageObj.MessageLengthUtf8 or messageObj.MessageLength
					cMessageObj.Message = string.rep("_", messageLength)
				else
					cMessageObj.Message = message
				end
				cMessageObj.IsFiltered = true
				-- We need to claim the message is filtered even if it not in this case for compatibility with legacy client side code.
				speaker:InternalSendMessage(cMessageObj, self.Name)
			else
				speaker:InternalSendMessage(messageObj, self.Name)
			end
		end
	end

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

	local textFilterContext = self.Private and Enum.TextFilterContext.PrivateChat or Enum.TextFilterContext.PublicChat
	local filterSuccess, isFilterResult, filteredMessage = self.ChatService:InternalApplyRobloxFilterNewAPI(
		messageObj.FromSpeaker,
		message,
		textFilterContext
	)
	if (filterSuccess) then
		messageObj.FilterResult = filteredMessage
		messageObj.IsFilterResult = isFilterResult
	else
		if UserFlagRemoveMessageOnTextFilterFailures then
			messageObj.IsFilterResult = false
			messageObj.FilterResult = ""
			messageObj.MessageLength = 0
		else
			return false
		end
	end
	messageObj.IsFiltered = true
	self:InternalAddMessageToHistoryLog(messageObj)

	for _, speakerName in pairs(sentToList) do
		local speaker = self.Speakers[speakerName]
		if (speaker) then
			speaker:InternalSendFilteredMessageWithFilterResult(messageObj, self.Name)
		end
	end

	-- One more pass is needed to ensure that no speakers do not recieve the message.
	-- Otherwise a user could join while the message is being filtered who had not originally been sent the message.
	local speakersMissingMessage = {}
	for _, speaker in pairs(self.Speakers) do
		local isMuted = speaker:IsSpeakerMuted(fromSpeaker.Name)
		if not isMuted and self:CanCommunicate(fromSpeaker, speaker) then
			local wasSentMessage = false
			for _, sentSpeakerName in pairs(sentToList) do
				if speaker.Name == sentSpeakerName then
					wasSentMessage = true
					break
				end
			end
			if not wasSentMessage then
				table.insert(speakersMissingMessage, speaker.Name)
			end
		end
	end

	for _, speakerName in pairs(speakersMissingMessage) do
		local speaker = self.Speakers[speakerName]
		if speaker then
			speaker:InternalSendFilteredMessageWithFilterResult(messageObj, self.Name)
		end
	end

	return messageObj
end

function methods:InternalAddSpeaker(speaker)
	if (self.Speakers[speaker.Name]) then
		warn("Speaker \"" .. speaker.name .. "\" is already in the channel!")
		return
	end

	self.Speakers[speaker.Name] = speaker
	local success, err = pcall(function() self.eSpeakerJoined:Fire(speaker.Name) end)
	if not success and err then
		print("Error removing channel: " ..err)
	end
end

function methods:InternalRemoveSpeaker(speaker)
	if (not self.Speakers[speaker.Name]) then
		warn("Speaker \"" .. speaker.name .. "\" is not in the channel!")
		return
	end

	self.Speakers[speaker.Name] = nil
	local success, err = pcall(function() self.eSpeakerLeft:Fire(speaker.Name) end)
	if not success and err then
		print("Error removing speaker: " ..err)
	end
end

function methods:InternalRemoveExcessMessagesFromLog()
	local remove = table.remove
	while (#self.ChatHistory > self.MaxHistory) do
		remove(self.ChatHistory, 1)
	end
end

function methods:InternalAddMessageToHistoryLog(messageObj)
	table.insert(self.ChatHistory, messageObj)

	self:InternalRemoveExcessMessagesFromLog()
end

function methods:GetMessageType(message, fromSpeaker)
	if fromSpeaker == nil then
		return ChatConstants.MessageTypeSystem
	end
	return ChatConstants.MessageTypeDefault
end

function methods:InternalCreateMessageObject(message, fromSpeaker, isFiltered, extraData)
	local messageType = self:GetMessageType(message, fromSpeaker)

	local speakerUserId = -1
	local speakerDisplayName = nil
	local speaker = nil

	if fromSpeaker then
		speaker = self.ChatService:GetSpeaker(fromSpeaker)
		if speaker then
			local player = speaker:GetPlayer()
			if player then
				speakerUserId = player.UserId

				if ChatSettings.PlayerDisplayNamesEnabled then
					speakerDisplayName = speaker:GetNameForDisplay()
				end
			else
				speakerUserId = 0
			end
		end
	end

	local messageObj =
	{
		ID = self.ChatService:InternalGetUniqueMessageId(),
		FromSpeaker = fromSpeaker,
		SpeakerDisplayName = speakerDisplayName,
		SpeakerUserId = speakerUserId,
		OriginalChannel = self.Name,
		MessageLength = string.len(message),
		MessageLengthUtf8 = utf8.len(utf8.nfcnormalize(message)),
		MessageType = messageType,
		IsFiltered = isFiltered,
		Message = isFiltered and message or nil,
		--// These two get set by the new API. The comments are just here
		--// to remind readers that they will exist so it's not super
		--// confusing if they find them in the code but cannot find them
		--// here.
		--FilterResult = nil,
		--IsFilterResult = false,
		Time = os.time(),
		ExtraData = {},
	}

	if speaker then
		for k, v in pairs(speaker.ExtraData) do
			messageObj.ExtraData[k] = v
		end
	end

	if (extraData) then
		for k, v in pairs(extraData) do
			messageObj.ExtraData[k] = v
		end
	end

	return messageObj
end

function methods:SetChannelNameColor(color)
	self.ChannelNameColor = color
	for i, speaker in pairs(self.Speakers) do
		speaker:UpdateChannelNameColor(self.Name, color)
	end
end

function methods:GetWelcomeMessageForSpeaker(speaker)
	if self.GetWelcomeMessageFunction then
		local welcomeMessage = self.GetWelcomeMessageFunction(speaker)
		if welcomeMessage then
			return welcomeMessage
		end
	end
	return self.WelcomeMessage
end

function methods:RegisterGetWelcomeMessageFunction(func)
	if type(func) ~= "function" then
		error("RegisterGetWelcomeMessageFunction must be called with a function.")
	end
	self.GetWelcomeMessageFunction = func
end

function methods:UnRegisterGetWelcomeMessageFunction()
	self.GetWelcomeMessageFunction = nil
end
