local i={}i.__index=i function i.SendSystemMessage(Y,i,M)local U=Y:InternalCreateMessageObject(i,nil,true,M)local X,C=pcall(function()Y.eMessagePosted:Fire(U)end)if not X and C then print("\069\114\114\111\114 \112\111\115\116\105\110\103 \109\101\115\115\097\103\101\058 "..C)end Y:InternalAddMessageToHistoryLog(U)for i,M in pairs(Y.Speakers)do M:InternalSendSystemMessage(U,Y.Name)end return U end function i.SendSystemMessageToSpeaker(U,i,M,Y)local X=U.Speakers[M]if X then local M=U:InternalCreateMessageObject(i,nil,true,Y)X:InternalSendSystemMessage(M,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",M,U.Name))end end function i.SendMessageObjToFilters(U,i,M,Y)local X=M.Message M.Message=i U:InternalDoMessageFilter(Y.Name,M,U.Name)U.ChatService:InternalDoMessageFilter(Y.Name,M,U.Name)local C=M.Message M.Message=X return C end function i.CanCommunicateByUserId(Y,i,M)if RunService:IsStudio()then return true end if i==0 or M==0 then return true end local U,X=pcall(function()return Chat:CanUsersChatAsync(i,M)end)return U and X end function i.CanCommunicate(Y,i,M)local U=i:GetPlayer()local X=M:GetPlayer()if U and X then return Y:CanCommunicateByUserId(U.UserId,X.UserId)end return true end function i.SendMessageToSpeaker(X,i,M,Y,U)local C=X.Speakers[M]local I=X.ChatService:GetSpeaker(Y)if C and I then local a=C:IsSpeakerMuted(Y)if a then return end if not X:CanCommunicate(C,I)then return end local n=M==Y local B=X:InternalCreateMessageObject(i,Y,n,U)i=X:SendMessageObjToFilters(i,B,Y)C:InternalSendMessage(B,X.Name)local L=X.ChatService:InternalApplyRobloxFilter(B.FromSpeaker,i,M)if L then B.Message=L B.IsFiltered=true C:InternalSendFilteredMessage(B,X.Name)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",M,X.Name))end end function i.KickSpeaker(Y,i,M)local U=Y.ChatService:GetSpeaker(i)if not U then error("\083\112\101\097\107\101\114 \""..(i.."\" \100\111\101\115 \110\111\116 \101\120\105\115\116\033"))end local X=""local C=""if M then X=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",Y.Name,M)C=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",i,M)else X=string.format("\089\111\117 \119\101\114\101 \107\105\099\107\101\100 \102\114\111\109 \'\037\115\'",Y.Name)C=string.format("\037\115 \119\097\115 \107\105\099\107\101\100",i)end Y:SendSystemMessageToSpeaker(X,i)U:LeaveChannel(Y.Name)Y:SendSystemMessage(C)end function i.MuteSpeaker(U,i,M,Y)local X=U.ChatService:GetSpeaker(i)if not X then error("\083\112\101\097\107\101\114 \""..(i.."\" \100\111\101\115 \110\111\116 \101\120\105\115\116\033"))end U.Mutes[i:lower()]=(Y==0 or Y==nil)and 0 or os.time()+Y if M 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",i,M))end local C,I=pcall(function()U.eSpeakerMuted:Fire(i,M,Y)end)if not C and I then print("\069\114\114\111\114 \109\117\116\116\105\110\103 \115\112\101\097\107\101\114\058 "..I)end local a=U.ChatService:GetSpeaker(i)if a then local i,X=pcall(function()a.eMuted:Fire(U.Name,M,Y)end)if not i and X then print("\069\114\114\111\114 \109\117\116\116\105\110\103 \115\112\101\097\107\101\114\058 "..X)end end end function i.UnmuteSpeaker(M,i)local Y=M.ChatService:GetSpeaker(i)if not Y then error("\083\112\101\097\107\101\114 \""..(i.."\" \100\111\101\115 \110\111\116 \101\120\105\115\116\033"))end M.Mutes[i:lower()]=nil local U,X=pcall(function()M.eSpeakerUnmuted:Fire(i)end)if not U and X then print("\069\114\114\111\114 \117\110\109\117\116\105\110\103 \115\112\101\097\107\101\114\058 "..X)end local C=M.ChatService:GetSpeaker(i)if C then local i,Y=pcall(function()C.eUnmuted:Fire(M.Name)end)if not i and Y then print("\069\114\114\111\114 \117\110\109\117\116\105\110\103 \115\112\101\097\107\101\114\058 "..Y)end end end function i.IsSpeakerMuted(M,i)return M.Mutes[i:lower()]~=nil end function i.GetSpeakerList(i)local M={}for i,Y in pairs(i.Speakers)do table.insert(M,Y.Name)end return M end function i.RegisterFilterMessageFunction(U,i,M,Y)if U.FilterMessageFunctions:HasFunction(i)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",i))end U.FilterMessageFunctions:AddFunction(i,M,Y)end function i.FilterMessageFunctionExists(M,i)return M.FilterMessageFunctions:HasFunction(i)end function i.UnregisterFilterMessageFunction(M,i)if not M.FilterMessageFunctions:HasFunction(i)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",i))end M.FilterMessageFunctions:RemoveFunction(i)end function i.RegisterProcessCommandsFunction(U,i,M,Y)if U.ProcessCommandsFunctions:HasFunction(i)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",i))end U.ProcessCommandsFunctions:AddFunction(i,M,Y)end function i.ProcessCommandsFunctionExists(M,i)return M.ProcessCommandsFunctions:HasFunction(i)end function i.UnregisterProcessCommandsFunction(M,i)if not M.ProcessCommandsFunctions:HasFunction(i)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",i))end M.ProcessCommandsFunctions:RemoveFunction(i)end local function M(i)local Y={}for U,X in pairs(i)do if type(X)==i then Y[U]=M(X)else Y[U]=X end end return Y end function i.GetHistoryLog(i)return M(i.ChatHistory)end function i.GetHistoryLogForSpeaker(Y,i)local U=-1 local X=i:GetPlayer()if X then U=X.UserId end local C={}for i=1,#Y.ChatHistory,1 do local X=Y.ChatHistory[i].SpeakerUserId if Y:CanCommunicateByUserId(U,X)then table.insert(C,M(Y.ChatHistory[i]))end end return C end
--////////////////////////////// Methods
--//////////////////////////////////////

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)

		local filteredMessage = self.ChatService:InternalApplyRobloxFilter(messageObj.FromSpeaker, message, speakerName)
		if filteredMessage then
			messageObj.Message = filteredMessage
			messageObj.IsFiltered = true
			speakerTo:InternalSendFilteredMessage(messageObj, self.Name)
		end
	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 DeepCopy(table)
	local copy = {}
	for i, v in pairs(table) do
		if type(v) == table then
			copy[i] = DeepCopy(v)
		else
			copy[i] = v
		end
	end
	return copy
end

function methods:GetHistoryLog()
	return DeepCopy(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
			table.insert(chatlog, DeepCopy(self.ChatHistory[i]))
		end
	end
	return chatlog
end
