UNIVERSAL CHAT
Key RequiredUniversal

UNIVERSAL CHAT

Universal Script4P_4P4P_4P 754 views 254 executesUpdated Apr 7, 2026
Unrated
00
Lua Script•340 lines (11905 chars)
--// Global Chat GUI (2x smaller, heartbeat online tracking)

local Players = game:GetService("Players")
local HttpService = game:GetService("HttpService")
local UserInputService = game:GetService("UserInputService")

local LocalPlayer = Players.LocalPlayer
local PlayerGui = LocalPlayer:WaitForChild("PlayerGui")

local API_BASE = "https://mii-response-server--721843.replit.app/api"
local JOIN_URL       = API_BASE .. "/chat/join"
local LEAVE_URL      = API_BASE .. "/chat/leave"
local SEND_URL       = API_BASE .. "/chat/send"
local MESSAGES_URL   = API_BASE .. "/chat/messages"
local ONLINE_URL     = API_BASE .. "/chat/online"
local HEARTBEAT_URL  = API_BASE .. "/chat/heartbeat"

local POLL_INTERVAL      = 2
local ONLINE_INTERVAL    = 5
local HEARTBEAT_INTERVAL = 10
local MAX_MESSAGE_LEN    = 300

local function jsonDecodeSafe(str)
	local ok, r = pcall(function() return HttpService:JSONDecode(str) end)
	return ok and r or nil
end

local function jsonEncodeSafe(tbl)
	local ok, r = pcall(function() return HttpService:JSONEncode(tbl) end)
	return ok and r or nil
end

local function httpRequest(method, url, bodyTable)
	local body = bodyTable and jsonEncodeSafe(bodyTable) or nil

	if typeof(request) == "function" then
		local ok, res = pcall(function()
			return request({
				Url = url, Method = method,
				Headers = body and { ["Content-Type"] = "application/json" } or {},
				Body = body,
			})
		end)
		if ok and res then
			local success = res.Success or (res.StatusCode and res.StatusCode >= 200 and res.StatusCode < 300)
			return success, res.Body or ""
		end
	end

	if method == "GET" then
		local ok, res = pcall(function() return game:HttpGet(url) end)
		return ok, res
	elseif method == "POST" then
		local ok, res = pcall(function() return game:HttpPost(url, body, "application/json") end)
		return ok, res
	end
	return false, "Unsupported method"
end

local function escapeRichText(text)
	text = tostring(text or "")
	text = text:gsub("&","&amp;"):gsub("<","&lt;"):gsub(">","&gt;")
	return text
end

local function trim(text)
	return (tostring(text or ""):gsub("^%s*(.-)%s*$", "%1"))
end

local function formatTime(ts)
	local ok, r = pcall(function()
		return os.date("!%H:%M", math.floor(tonumber(ts) or os.time()))
	end)
	return ok and r or "--:--"
end

-- GUI
local gui = Instance.new("ScreenGui")
gui.Name = "GlobalChatGui"
gui.ResetOnSpawn = false
gui.IgnoreGuiInset = true
gui.Parent = PlayerGui

local main = Instance.new("Frame")
main.AnchorPoint = Vector2.new(0.5, 0.5)
main.Position = UDim2.new(0.5, 0, 0.5, 0)
main.Size = UDim2.new(0.45, 0, 0.29, 0)
main.BackgroundColor3 = Color3.fromRGB(20, 20, 24)
main.BorderSizePixel = 0
main.Parent = gui

local c = Instance.new("UICorner"); c.CornerRadius = UDim.new(0, 8); c.Parent = main
local s = Instance.new("UIStroke"); s.Thickness = 1; s.Transparency = 0.5
s.Color = Color3.fromRGB(255,255,255); s.Parent = main
local lim = Instance.new("UISizeConstraint")
lim.MinSize = Vector2.new(140, 180); lim.MaxSize = Vector2.new(210, 280); lim.Parent = main

local topBar = Instance.new("Frame")
topBar.Size = UDim2.new(1, 0, 0, 20)
topBar.BackgroundColor3 = Color3.fromRGB(28, 28, 34)
topBar.BorderSizePixel = 0
topBar.Parent = main
local tc = Instance.new("UICorner"); tc.CornerRadius = UDim.new(0, 8); tc.Parent = topBar
local tm = Instance.new("Frame")
tm.Size = UDim2.new(1, 0, 0, 8); tm.Position = UDim2.new(0, 0, 1, -8)
tm.BackgroundColor3 = Color3.fromRGB(28,28,34); tm.BorderSizePixel = 0; tm.Parent = topBar

local titleLbl = Instance.new("TextLabel")
titleLbl.BackgroundTransparency = 1
titleLbl.Size = UDim2.new(1, -60, 1, 0); titleLbl.Position = UDim2.new(0, 7, 0, 0)
titleLbl.Font = Enum.Font.GothamBold; titleLbl.Text = "Global Chat"
titleLbl.TextColor3 = Color3.fromRGB(255,255,255); titleLbl.TextSize = 11
titleLbl.TextXAlignment = Enum.TextXAlignment.Left; titleLbl.Parent = topBar

local onlineLabel = Instance.new("TextLabel")
onlineLabel.BackgroundTransparency = 1
onlineLabel.Size = UDim2.new(0, 55, 1, 0); onlineLabel.Position = UDim2.new(1, -58, 0, 0)
onlineLabel.Font = Enum.Font.Gotham; onlineLabel.Text = "Online: 0"
onlineLabel.TextColor3 = Color3.fromRGB(200,200,200); onlineLabel.TextSize = 10
onlineLabel.TextXAlignment = Enum.TextXAlignment.Right; onlineLabel.Parent = topBar

local statusLabel = Instance.new("TextLabel")
statusLabel.BackgroundTransparency = 1
statusLabel.Size = UDim2.new(1, -14, 0, 12); statusLabel.Position = UDim2.new(0, 7, 0, 23)
statusLabel.Font = Enum.Font.Gotham; statusLabel.Text = "Connecting..."
statusLabel.TextColor3 = Color3.fromRGB(180,180,180); statusLabel.TextSize = 10
statusLabel.TextXAlignment = Enum.TextXAlignment.Left; statusLabel.Parent = main

local messagesFrame = Instance.new("ScrollingFrame")
messagesFrame.Size = UDim2.new(1, -14, 1, -66)
messagesFrame.Position = UDim2.new(0, 7, 0, 38)
messagesFrame.BackgroundColor3 = Color3.fromRGB(16,16,20)
messagesFrame.BorderSizePixel = 0; messagesFrame.ScrollBarThickness = 3
messagesFrame.CanvasSize = UDim2.new(0,0,0,0)
messagesFrame.AutomaticCanvasSize = Enum.AutomaticSize.Y
messagesFrame.ScrollingDirection = Enum.ScrollingDirection.Y
messagesFrame.Parent = main
local mc = Instance.new("UICorner"); mc.CornerRadius = UDim.new(0, 7); mc.Parent = messagesFrame
local mp = Instance.new("UIPadding")
mp.PaddingTop = UDim.new(0,6); mp.PaddingBottom = UDim.new(0,6)
mp.PaddingLeft = UDim.new(0,6); mp.PaddingRight = UDim.new(0,6); mp.Parent = messagesFrame
local layout = Instance.new("UIListLayout")
layout.Padding = UDim.new(0, 4); layout.SortOrder = Enum.SortOrder.LayoutOrder
layout.Parent = messagesFrame

local inputFrame = Instance.new("Frame")
inputFrame.Size = UDim2.new(1, -14, 0, 22); inputFrame.Position = UDim2.new(0, 7, 1, -28)
inputFrame.BackgroundTransparency = 1; inputFrame.Parent = main

local textBox = Instance.new("TextBox")
textBox.Size = UDim2.new(1, -52, 1, 0)
textBox.BackgroundColor3 = Color3.fromRGB(28,28,34); textBox.BorderSizePixel = 0
textBox.ClearTextOnFocus = false; textBox.PlaceholderText = "Type a message..."
textBox.Text = ""; textBox.Font = Enum.Font.Gotham; textBox.TextSize = 11
textBox.TextColor3 = Color3.fromRGB(255,255,255)
textBox.PlaceholderColor3 = Color3.fromRGB(150,150,150)
textBox.TextXAlignment = Enum.TextXAlignment.Left; textBox.Parent = inputFrame
local tbc = Instance.new("UICorner"); tbc.CornerRadius = UDim.new(0,6); tbc.Parent = textBox
local tbp = Instance.new("UIPadding")
tbp.PaddingLeft = UDim.new(0,6); tbp.PaddingRight = UDim.new(0,6); tbp.Parent = textBox

local sendButton = Instance.new("TextButton")
sendButton.Size = UDim2.new(0, 46, 1, 0); sendButton.Position = UDim2.new(1, -46, 0, 0)
sendButton.BackgroundColor3 = Color3.fromRGB(70,110,255); sendButton.BorderSizePixel = 0
sendButton.Text = "Send"; sendButton.Font = Enum.Font.GothamBold; sendButton.TextSize = 11
sendButton.TextColor3 = Color3.fromRGB(255,255,255); sendButton.Parent = inputFrame
local sc = Instance.new("UICorner"); sc.CornerRadius = UDim.new(0,6); sc.Parent = sendButton

local messageCount = 0

local function scrollToBottom()
	task.defer(function()
		messagesFrame.CanvasPosition = Vector2.new(0, math.huge)
	end)
end

local function addMessage(entry)
	if not entry then return end
	messageCount += 1
	local username = escapeRichText(entry.username or "Unknown")
	local message  = escapeRichText(entry.message or "")
	local timeStr  = formatTime(entry.timestamp)

	local row = Instance.new("Frame")
	row.BackgroundTransparency = 1
	row.Size = UDim2.new(1, 0, 0, 0)
	row.AutomaticSize = Enum.AutomaticSize.Y
	row.LayoutOrder = messageCount
	row.Parent = messagesFrame

	local label = Instance.new("TextLabel")
	label.BackgroundTransparency = 1
	label.Size = UDim2.new(1, 0, 0, 0)
	label.AutomaticSize = Enum.AutomaticSize.Y
	label.Font = Enum.Font.Gotham; label.TextSize = 11
	label.TextWrapped = true; label.RichText = true
	label.TextXAlignment = Enum.TextXAlignment.Left
	label.TextYAlignment = Enum.TextYAlignment.Top
	label.TextColor3 = Color3.fromRGB(240,240,240)
	label.Text = string.format(
		"<font color=\"#8ab4ff\">[%s]</font> <b>%s</b>: %s",
		timeStr, username, message
	)
	label.Parent = row
	scrollToBottom()
end

local function setStatus(text, color)
	statusLabel.Text = text
	statusLabel.TextColor3 = color or Color3.fromRGB(180,180,180)
end

-- Drag
local dragging, dragInput, dragStart, startPos = false, nil, nil, nil
topBar.InputBegan:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseButton1
		or input.UserInputType == Enum.UserInputType.Touch then
		dragging = true; dragStart = input.Position; startPos = main.Position
		input.Changed:Connect(function()
			if input.UserInputState == Enum.UserInputState.End then dragging = false end
		end)
	end
end)
topBar.InputChanged:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseMovement
		or input.UserInputType == Enum.UserInputType.Touch then dragInput = input end
end)
UserInputService.InputChanged:Connect(function(input)
	if dragging and input == dragInput then
		local delta = input.Position - dragStart
		main.Position = UDim2.new(
			startPos.X.Scale, startPos.X.Offset + delta.X,
			startPos.Y.Scale, startPos.Y.Offset + delta.Y
		)
	end
end)

local lastId = 0
local sending = false
local alive   = true

local function getMessages(afterId, limit)
	local url = string.format("%s?after=%d&limit=%d", MESSAGES_URL, afterId, limit)
	local ok, res = httpRequest("GET", url)
	if not ok then return nil end
	return jsonDecodeSafe(res)
end

local function refreshOnline()
	local ok, res = httpRequest("GET", ONLINE_URL)
	if not ok then return end
	local data = jsonDecodeSafe(res)
	if data and data.count ~= nil then
		onlineLabel.Text = "Online: " .. tostring(data.count)
	end
end

local function sendChatMessage()
	if sending then return end
	local text = trim(textBox.Text)
	if text == "" then return end
	if #text > MAX_MESSAGE_LEN then text = text:sub(1, MAX_MESSAGE_LEN) end

	sending = true; sendButton.Text = "..."; sendButton.Active = false

	local ok = httpRequest("POST", SEND_URL, {
		username = LocalPlayer.Name,
		userId   = tostring(LocalPlayer.UserId),
		message  = text,
	})

	if ok then textBox.Text = ""; setStatus("Sent", Color3.fromRGB(120,255,160))
	else setStatus("Send failed", Color3.fromRGB(255,120,120)) end

	sendButton.Text = "Send"; sendButton.Active = true; sending = false
end

sendButton.MouseButton1Click:Connect(sendChatMessage)
textBox.FocusLost:Connect(function(enter) if enter then sendChatMessage() end end)

-- Initial load + join
task.spawn(function()
	httpRequest("POST", JOIN_URL, {
		username = LocalPlayer.Name,
		userId   = tostring(LocalPlayer.UserId),
	})
	refreshOnline()

	local data = getMessages(0, 50)
	if data and data.messages then
		table.sort(data.messages, function(a,b) return (tonumber(a.id) or 0) < (tonumber(b.id) or 0) end)
		for _, msg in ipairs(data.messages) do
			addMessage(msg)
			lastId = math.max(lastId, tonumber(msg.id) or 0)
		end
		scrollToBottom()
	end
	setStatus("Connected", Color3.fromRGB(120,255,160))
end)

-- Poll messages
task.spawn(function()
	while alive do
		task.wait(POLL_INTERVAL)
		local data = getMessages(lastId, 50)
		if data and data.messages then
			for _, msg in ipairs(data.messages) do
				local id = tonumber(msg.id) or 0
				if id > lastId then addMessage(msg); lastId = id end
			end
		end
	end
end)

-- Refresh online count
task.spawn(function()
	while alive do
		task.wait(ONLINE_INTERVAL)
		refreshOnline()
	end
end)

-- Heartbeat — keeps this player in the online list
task.spawn(function()
	while alive do
		task.wait(HEARTBEAT_INTERVAL)
		httpRequest("POST", HEARTBEAT_URL, { userId = tostring(LocalPlayer.UserId) })
	end
end)

-- Leave on close
pcall(function()
	game:BindToClose(function()
		alive = false
		httpRequest("POST", LEAVE_URL, {
			username = LocalPlayer.Name,
			userId   = tostring(LocalPlayer.UserId),
		})
	end)
end)

Features & Information

Universal chat with working servers allowing multiple hackers to chat across games
#Universal#Chat

Community Comments (0)

No comments yet for this script.

More Scripts for Universal Scripts