function methods.InternalDestroy(T)for E,i in pairs(T.Speakers)do i:LeaveChannel(T.Name)end T.eDestroyed:Fire()T.eDestroyed:Destroy()T.eMessagePosted:Destroy()T.eSpeakerJoined:Destroy()T.eSpeakerLeft:Destroy()T.eSpeakerMuted:Destroy()T.eSpeakerUnmuted:Destroy()end function methods.InternalDoMessageFilter(C,T,E,i)local W=C.FilterMessageFunctions:GetIterator()for C,W,r in W do local w,J=pcall(function()W(T,E,i)end)if not w 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",C,J))end end end function methods.InternalDoProcessCommands(C,T,E,i)local W=C.ProcessCommandsFunctions:GetIterator()for C,W,r in W do local w,J=pcall(function()local C=W(T,E,i)if type(C)~="\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 C end)if not w 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",C,J))elseif J then return true end end return false end function methods.InternalPostMessage(C,T,E,i)if C:InternalDoProcessCommands(T.Name,E,C.Name)then return false end if C.Mutes[T.Name:lower()]~=nil then local E=C.Mutes[T.Name:lower()]if E>0 and os.time()>E then C:UnmuteSpeaker(T.Name)else C: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"),T.Name)return false end end local W=C:InternalCreateMessageObject(E,T.Name,false,i)W.Message=E local r pcall(function()r=Chat:InvokeChatCallback(Enum.ChatCallbackType.OnServerReceivingMessage,W)end)W.Message=nil if r then if r.ShouldDeliver==false then return false end W=r end E=C:SendMessageObjToFilters(E,W,T)local w={}for i,r in pairs(C.Speakers)do local J=r:IsSpeakerMuted(T.Name)if not J and C:CanCommunicate(T,r)then table.insert(w,r.Name)if r.Name==T.Name then local T=ShallowCopy(W)if userShouldMuteUnfilteredMessage then T.Message=string.rep("\095",W.MessageLength)else T.Message=E end T.IsFiltered=true r:InternalSendMessage(T,C.Name)else r:InternalSendMessage(W,C.Name)end end end local J,K=pcall(function()C.eMessagePosted:Fire(W)end)if not J and K then print("\069\114\114\111\114 \112\111\115\116\105\110\103 \109\101\115\115\097\103\101\058 "..K)end local z=C.Private and Enum.TextFilterContext.PrivateChat or Enum.TextFilterContext.PublicChat local d,y,Y=C.ChatService:InternalApplyRobloxFilterNewAPI(W.FromSpeaker,E,z)if d then W.FilterResult=Y W.IsFilterResult=y else return false end W.IsFiltered=true C:InternalAddMessageToHistoryLog(W)for T,E in pairs(w)do local i=C.Speakers[E]if i then i:InternalSendFilteredMessageWithFilterResult(W,C.Name)end end local c={}for E,i in pairs(C.Speakers)do local W=i:IsSpeakerMuted(T.Name)if not W and C:CanCommunicate(T,i)then local T=false for E,C in pairs(w)do if i.Name==C then T=true break end end if not T then table.insert(c,i.Name)end end end for T,E in pairs(c)do local i=C.Speakers[E]if i then i:InternalSendFilteredMessageWithFilterResult(W,C.Name)end end return W end function methods.InternalAddSpeaker(E,T)if E.Speakers[T.Name]then warn("\083\112\101\097\107\101\114 \""..(T.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 E.Speakers[T.Name]=T local i,C=pcall(function()E.eSpeakerJoined:Fire(T.Name)end)if not i and C then print("\069\114\114\111\114 \114\101\109\111\118\105\110\103 \099\104\097\110\110\101\108\058 "..C)end end function methods.InternalRemoveSpeaker(E,T)if not E.Speakers[T.Name]then warn("\083\112\101\097\107\101\114 \""..(T.name.."\" \105\115 \110\111\116 \105\110 \116\104\101 \099\104\097\110\110\101\108\033"))return end E.Speakers[T.Name]=nil local i,C=pcall(function()E.eSpeakerLeft:Fire(T.Name)end)if not i and C then print("\069\114\114\111\114 \114\101\109\111\118\105\110\103 \115\112\101\097\107\101\114\058 "..C)end end function methods.InternalRemoveExcessMessagesFromLog(T)local E=table.remove while#T.ChatHistory>T.MaxHistory do E(T.ChatHistory,1)end end function methods.InternalAddMessageToHistoryLog(E,T)table.insert(E.ChatHistory,T)E:InternalRemoveExcessMessagesFromLog()end function methods.GetMessageType(i,T,E)if E==nil then return ChatConstants.MessageTypeSystem end return ChatConstants.MessageTypeDefault end function methods.InternalCreateMessageObject(W,T,E,i,C)local r=W:GetMessageType(T,E)local w=-1 local J=nil local K=nil if E then K=W.ChatService:GetSpeaker(E)if K then local T=K:GetPlayer()if T then w=T.UserId if ChatSettings.PlayerDisplayNamesEnabled then J=K:GetNameForDisplay()end else w=0 end end end local z={ID=W.ChatService:InternalGetUniqueMessageId(),FromSpeaker=E,SpeakerDisplayName=J;SpeakerUserId=w;OriginalChannel=W.Name,MessageLength=string.len(T);MessageType=r,IsFiltered=i;Message=i and T or nil,Time=os.time(),ExtraData={}}if K then for T,E in pairs(K.ExtraData)do z.ExtraData[T]=E end end if C then for T,E in pairs(C)do z.ExtraData[T]=E end end return z end function methods.SetChannelNameColor(E,T)E.ChannelNameColor=T for i,C in pairs(E.Speakers)do C:UpdateChannelNameColor(E.Name,T)end end function methods.GetWelcomeMessageForSpeaker(E,T)if E.GetWelcomeMessageFunction then local i=E.GetWelcomeMessageFunction(T)if i then return i end end return E.WelcomeMessage end function methods.RegisterGetWelcomeMessageFunction(E,T)if type(T)~="\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 E.GetWelcomeMessageFunction=T end function methods.UnRegisterGetWelcomeMessageFunction(T)T.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
					cMessageObj.Message = string.rep("_", messageObj.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
		return false
	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),
		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
