solara hub
Key RequiredUniversal

solara hub

Universal ScriptSilimanSiliman 364 views 949 executesUpdated Aug 17, 2026
Unrated
00
Lua Script•909 lines (29892 chars)
-- Services
local CoreGui = game:GetService("CoreGui")
local Players = game:GetService("Players")
local StarterGui = game:GetService("StarterGui")
local UserInputService = game:GetService("UserInputService")
local HttpService = game:GetService("HttpService")
local TeleportService = game:GetService("TeleportService")
local LocalPlayer = Players.LocalPlayer

---------------------------------------------------------
-- [ RP & BIO SYSTEM ]
---------------------------------------------------------
local function UpdateRP()
	pcall(function()
		local RE = game:GetService("ReplicatedStorage"):WaitForChild("RE", 5)
		if RE then
			RE:FindFirstChild("1RPNam1eTex1t"):FireServer("RolePlayName", "ȤeNix CHaT")
			RE:FindFirstChild("1RPNam1eTex1t"):FireServer("RolePlayBio", "By ZaIN, J00")
			RE:FindFirstChild("1RPNam1eColo1r"):FireServer("PickingRPNameColor", Color3.fromRGB(170, 85, 255))
			pcall(function()
				RE:FindFirstChild("1RPNam1eColo1r"):FireServer("PickingRPBioColor", Color3.fromRGB(0, 100, 0))
			end)
		end
	end)
end

task.spawn(function()
    task.wait(5)
    local RE = game:GetService("ReplicatedStorage"):WaitForChild("RE", 5)
    if RE then
        RE:FindFirstChild("1RPNam1eTex1t"):FireServer("RolePlayName", "ZeNiX CHaT π foR aLL")
        RE:FindFirstChild("1RPNam1eTex1t"):FireServer("RolePlayBio", "By ZaIN, J00")
        local Green = Color3.fromRGB(0, 255, 0)
        local Red = Color3.fromRGB(255, 0, 0)
        RE:FindFirstChild("1RPNam1eColo1r"):FireServer("PickingRPNameColor", Green)
        RE:FindFirstChild("1RPNam1eColo1r"):FireServer("PickingRPBioColor", Red)
    end
end)

---------------------------------------------------------

-- CONFIGURATION
local FIREBASE_URL = "https://betterchat-rblx-default-rtdb.firebaseio.com/"
local CURRENT_VERSION_STRING = "2"
local FOLDER_NAME = "BetterChat"
local VERSION_FILE = FOLDER_NAME .. "/version.txt"
local RUNS_FILE = FOLDER_NAME .. "/runs.txt"
local CHAT_PATH = "GlobalChat/Servers/" .. (game.JobId ~= "" and game.JobId or "Studio_Debug")
local PRESENCE_PATH = "GlobalChat/OnlinePlayers"
local ANNOUNCE_PATH = "GlobalChat/Announcement"
local MAX_MESSAGES = 50
local PRESENCE_TTL = 90

-- FIX 1: safeRequest no longer falls back to HttpService
local function safeRequest(options)
	local req = (syn and syn.request) or (http and http.request) or http_request or request
	if req then
		return req(options)
	else
		return {Success = false, Body = ""}
	end
end

local function sanitize(s)
	return tostring(s):gsub("[<>&]", {["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;"})
end

-- 1. BOOTSTRAPPER
if not isfolder(FOLDER_NAME) then makefolder(FOLDER_NAME) end

local function CheckForUpdates()
	local response = safeRequest({
		Url = FIREBASE_URL .. "GlobalChat/LatestVersion.json",
		Method = "GET"
	})
	if not response.Success then return false end
	if response.Body ~= "null" then
		local cloudVersion = string.gsub(response.Body, '["%s]', '')
		if CURRENT_VERSION_STRING ~= cloudVersion then return false end
	end
	return true
end

if not CheckForUpdates() then
	local parentGui = (gethui and gethui()) or CoreGui:FindFirstChild("RobloxGui") or CoreGui
	local UpdateGui = Instance.new("ScreenGui", parentGui)
	UpdateGui.Name = "BetterChatUpdater"
	local BG = Instance.new("Frame", UpdateGui)
	BG.Size = UDim2.new(1, 0, 1, 0)
	BG.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
	local Warning = Instance.new("TextLabel", BG)
	Warning.Size = UDim2.new(1, 0, 1, 0)
	Warning.Text = "Script outdated, please update."
	Warning.TextColor3 = Color3.fromRGB(255, 80, 80)
	Warning.TextSize = 30
	Warning.BackgroundTransparency = 1
	return
end

writefile(VERSION_FILE, CURRENT_VERSION_STRING)

-- 2. RUN COUNTER
local runCount = 0
if isfile(RUNS_FILE) then
	runCount = tonumber(readfile(RUNS_FILE)) or 0
end
runCount = runCount + 1
writefile(RUNS_FILE, tostring(runCount))
local isFirstRun = runCount == 1

-- 3. PRESENCE SYSTEM
local MY_JOB_ID = game.JobId ~= "" and game.JobId or "Studio_Debug"
local MY_PLACE_ID = game.PlaceId

local function WritePresence()
	safeRequest({
		Url = FIREBASE_URL .. PRESENCE_PATH .. "/" .. LocalPlayer.UserId .. ".json",
		Method = "PUT",
		Headers = {["Content-Type"] = "application/json"},
		Body = HttpService:JSONEncode({
			DisplayName = LocalPlayer.DisplayName,
			Username    = LocalPlayer.Name,
			UserId      = LocalPlayer.UserId,
			JobId       = MY_JOB_ID,
			PlaceId     = MY_PLACE_ID,
			LastSeen    = os.time()
		})
	})
end

local function ClearPresence()
	safeRequest({
		Url = FIREBASE_URL .. PRESENCE_PATH .. "/" .. LocalPlayer.UserId .. ".json",
		Method = "DELETE"
	})
end

WritePresence()
task.spawn(function()
	while task.wait(30) do WritePresence() end
end)

LocalPlayer.AncestryChanged:Connect(function()
	if not LocalPlayer:IsDescendantOf(game) then ClearPresence() end
end)

-- 4. UI SETUP
local parentGui = (gethui and gethui()) or CoreGui:FindFirstChild("RobloxGui") or CoreGui
if parentGui:FindFirstChild("GlobalChatGui") then parentGui.GlobalChatGui:Destroy() end


local ScreenGui = Instance.new("ScreenGui", parentGui)
ScreenGui.Name = "GlobalChatGui"
ScreenGui.IgnoreGuiInset = true
ScreenGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling

local Container = Instance.new("Frame", ScreenGui)
Container.Name = "mainframe"
Container.BackgroundColor3 = Color3.new(0, 0, 0)
Container.BackgroundTransparency = 0.2
Container.BorderColor3 = Color3.new(0, 0, 0)
Container.BorderSizePixel = 0
Container.Position = UDim2.new(0, 10, 0, 60)
Container.Size = UDim2.new(0, 340, 0, 202)
Container.Visible = true
local ContainerCorner = Instance.new("UICorner", Container)
ContainerCorner.CornerRadius = UDim.new(0, 9)

local ChatScroll = Instance.new("ScrollingFrame", Container)
ChatScroll.BackgroundTransparency = 1
ChatScroll.Position = UDim2.new(0, 8, 0, 6)
ChatScroll.Size = UDim2.new(1, -16, 1, -48)
ChatScroll.AutomaticCanvasSize = Enum.AutomaticSize.Y
ChatScroll.CanvasSize = UDim2.new(0, 0, 0, 0)
ChatScroll.ScrollBarThickness = 2
ChatScroll.ScrollBarImageColor3 = Color3.fromRGB(255, 255, 255)
local Layout = Instance.new("UIListLayout", ChatScroll)
Layout.Padding = UDim.new(0, 4)

local TextBox = Instance.new("TextBox", Container)
TextBox.BackgroundColor3 = Color3.new(0, 0, 0)
TextBox.BackgroundTransparency = 0.5
TextBox.BorderColor3 = Color3.new(0, 0, 0)
TextBox.BorderSizePixel = 0
TextBox.Position = UDim2.new(0.0234042555, 0, 0.797029674, 0)
TextBox.Size = UDim2.new(0, 300, 0, 33)
TextBox.Font = Enum.Font.SourceSansBold
TextBox.PlaceholderText = "To chat, click here"
TextBox.Text = ""
TextBox.TextColor3 = Color3.new(1, 1, 1)
TextBox.TextSize = 23
TextBox.TextWrapped = true
TextBox.TextXAlignment = Enum.TextXAlignment.Left
TextBox.ClearTextOnFocus = false
Instance.new("UICorner", TextBox)

local ToggleButton = Instance.new("ImageButton", ScreenGui)
ToggleButton.Name = "ChatToggle"
ToggleButton.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
ToggleButton.BackgroundTransparency = 0.5
ToggleButton.AnchorPoint = Vector2.new(1, 0)
ToggleButton.Position = UDim2.new(1, -50, 0, 4)
ToggleButton.Size = UDim2.new(0, 32, 0, 32)
ToggleButton.Image = "rbxasset://textures/ui/Chat/Chat.png"
Instance.new("UICorner", ToggleButton).CornerRadius = UDim.new(0, 8)

local UnreadBadge = Instance.new("TextLabel", ScreenGui)
UnreadBadge.Name = "UnreadBadge"
UnreadBadge.BackgroundColor3 = Color3.fromRGB(255, 60, 60)
UnreadBadge.AnchorPoint = Vector2.new(1, 0)
UnreadBadge.Position = UDim2.new(1, -44, 0, 34)
UnreadBadge.Size = UDim2.new(0, 18, 0, 14)
UnreadBadge.Font = Enum.Font.GothamBold
UnreadBadge.Text = ""
UnreadBadge.TextColor3 = Color3.fromRGB(255, 255, 255)
UnreadBadge.TextSize = 9
UnreadBadge.TextXAlignment = Enum.TextXAlignment.Center
UnreadBadge.Visible = false
Instance.new("UICorner", UnreadBadge).CornerRadius = UDim.new(0, 4)

local OnlineButton = Instance.new("TextButton", ScreenGui)
OnlineButton.Name = "OnlineToggle"
OnlineButton.BackgroundColor3 = Color3.fromRGB(0, 180, 100)
OnlineButton.BackgroundTransparency = 0.4
OnlineButton.AnchorPoint = Vector2.new(1, 0)
OnlineButton.Position = UDim2.new(1, -88, 0, 4)
OnlineButton.Size = UDim2.new(0, 32, 0, 32)
OnlineButton.Text = "👥"
OnlineButton.TextSize = 18
Instance.new("UICorner", OnlineButton).CornerRadius = UDim.new(0, 8)

local OnlinePanel = Instance.new("Frame", ScreenGui)
OnlinePanel.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
OnlinePanel.BackgroundTransparency = 0.5
OnlinePanel.AnchorPoint = Vector2.new(0.5, 0.5)
OnlinePanel.Position = UDim2.new(0.5, 0, 0.5, 0)
OnlinePanel.Size = UDim2.new(0, 360, 0, 180)
OnlinePanel.Visible = false
Instance.new("UICorner", OnlinePanel)

local OnlineTitle = Instance.new("TextLabel", OnlinePanel)
OnlineTitle.BackgroundTransparency = 1
OnlineTitle.Position = UDim2.new(0, 8, 0, 4)
OnlineTitle.Size = UDim2.new(1, -16, 0, 20)
OnlineTitle.Font = Enum.Font.GothamBold
OnlineTitle.Text = "🟢 Online Players"
OnlineTitle.TextColor3 = Color3.fromRGB(100, 255, 150)
OnlineTitle.TextSize = 14
OnlineTitle.TextXAlignment = Enum.TextXAlignment.Left

local OnlineScroll = Instance.new("ScrollingFrame", OnlinePanel)
OnlineScroll.BackgroundTransparency = 1
OnlineScroll.Position = UDim2.new(0, 8, 0, 28)
OnlineScroll.Size = UDim2.new(1, -16, 1, -36)
OnlineScroll.AutomaticCanvasSize = Enum.AutomaticSize.Y
OnlineScroll.CanvasSize = UDim2.new(0, 0, 0, 0)
OnlineScroll.ScrollBarThickness = 2
local OnlineLayout = Instance.new("UIListLayout", OnlineScroll)
OnlineLayout.Padding = UDim.new(0, 4)

-- 5. UNREAD LOGIC
local unreadCount = 0

local function AddUnread()
	if Container.Visible then return end
	unreadCount += 1
	UnreadBadge.Text = unreadCount > 99 and "99+" or tostring(unreadCount)
	UnreadBadge.Visible = true
end

local function ClearUnread()
	unreadCount = 0
	UnreadBadge.Text = ""
	UnreadBadge.Visible = false
end

-- 6. CORE CHAT LOGIC

local function trimMessages()
	local labels = {}
	for _, c in ipairs(ChatScroll:GetChildren()) do
		if c:IsA("TextLabel") then table.insert(labels, c) end
	end
	table.sort(labels, function(a, b)
		return a.AbsolutePosition.Y < b.AbsolutePosition.Y
	end)
	if #labels > MAX_MESSAGES then
		for i = 1, #labels - MAX_MESSAGES do
			labels[i]:Destroy()
		end
	end
end

local function CreateMessage(sender, text, isSystem)
	local TextLabel = Instance.new("TextLabel", ChatScroll)
	TextLabel.BackgroundTransparency = 1
	TextLabel.Size = UDim2.new(1, 0, 0, 0)
	TextLabel.Font = Enum.Font.SourceSansBold
	TextLabel.TextSize = 16
	TextLabel.RichText = true
	TextLabel.TextWrapped = true
	TextLabel.AutomaticSize = Enum.AutomaticSize.Y
	TextLabel.TextXAlignment = Enum.TextXAlignment.Left
	local nameColor = isSystem and "rgb(255, 80, 80)" or "rgb(150, 200, 255)"
	TextLabel.Text = string.format(
		'<font color="%s"><b>%s:</b></font> <font color="rgb(255,255,255)">%s</font>',
		nameColor, sanitize(sender), sanitize(text)
	)
	trimMessages()
	task.defer(function()
		ChatScroll.CanvasPosition = Vector2.new(0, ChatScroll.AbsoluteCanvasSize.Y)
	end)
	if not isSystem then AddUnread() end
end

local function CreateAnnouncement(sender, text)
	local toast = Instance.new("Frame", ScreenGui)
	toast.BackgroundColor3 = Color3.fromRGB(130, 130, 130)
	toast.AnchorPoint = Vector2.new(0.5, 0)
	toast.Position = UDim2.new(0.5, 0, 0, 10)
	toast.Size = UDim2.new(0, 320, 0, 80)
	Instance.new("UICorner", toast).CornerRadius = UDim.new(1, 0)

	local leftBulge = Instance.new("Frame", toast)
	leftBulge.BackgroundColor3 = Color3.fromRGB(130, 130, 130)
	leftBulge.BorderSizePixel = 0
	leftBulge.AnchorPoint = Vector2.new(0.5, 0.5)
	leftBulge.Position = UDim2.new(0, 18, 0.5, 0)
	leftBulge.Size = UDim2.new(0, 72, 0, 72)
	Instance.new("UICorner", leftBulge).CornerRadius = UDim.new(1, 0)

	local rightBulge = Instance.new("Frame", toast)
	rightBulge.BackgroundColor3 = Color3.fromRGB(130, 130, 130)
	rightBulge.BorderSizePixel = 0
	rightBulge.AnchorPoint = Vector2.new(0.5, 0.5)
	rightBulge.Position = UDim2.new(1, -18, 0.5, 0)
	rightBulge.Size = UDim2.new(0, 72, 0, 72)
	Instance.new("UICorner", rightBulge).CornerRadius = UDim.new(1, 0)

	local senderLabel = Instance.new("TextLabel", toast)
	senderLabel.BackgroundTransparency = 1
	senderLabel.AnchorPoint = Vector2.new(0.5, 0.5)
	senderLabel.Position = UDim2.new(0.5, 0, 0.35, 0)
	senderLabel.Size = UDim2.new(0.8, 0, 0, 22)
	senderLabel.Font = Enum.Font.GothamMedium
	senderLabel.Text = sanitize(sender)
	senderLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
	senderLabel.TextSize = 14
	senderLabel.TextStrokeTransparency = 0
	senderLabel.TextStrokeColor3 = Color3.fromRGB(0, 0, 0)
	senderLabel.TextXAlignment = Enum.TextXAlignment.Center

	local msgLabel = Instance.new("TextLabel", toast)
	msgLabel.BackgroundTransparency = 1
	msgLabel.AnchorPoint = Vector2.new(0.5, 0.5)
	msgLabel.Position = UDim2.new(0.5, 0, 0.68, 0)
	msgLabel.Size = UDim2.new(0.8, 0, 0, 28)
	msgLabel.Font = Enum.Font.GothamBold
	msgLabel.Text = sanitize(text)
	msgLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
	msgLabel.TextSize = 20
	msgLabel.TextStrokeTransparency = 0
	msgLabel.TextStrokeColor3 = Color3.fromRGB(0, 0, 0)
	msgLabel.TextXAlignment = Enum.TextXAlignment.Center
	msgLabel.TextWrapped = true

	local chatLabel = Instance.new("TextLabel", ChatScroll)
	chatLabel.BackgroundColor3 = Color3.fromRGB(255, 200, 0)
	chatLabel.BackgroundTransparency = 0.85
	chatLabel.Size = UDim2.new(1, 0, 0, 0)
	chatLabel.Font = Enum.Font.SourceSansBold
	chatLabel.TextSize = 16
	chatLabel.RichText = true
	chatLabel.TextWrapped = true
	chatLabel.AutomaticSize = Enum.AutomaticSize.Y
	chatLabel.TextXAlignment = Enum.TextXAlignment.Left
	Instance.new("UICorner", chatLabel).CornerRadius = UDim.new(0, 4)
	chatLabel.Text = string.format(
		'<font color="rgb(255,200,0)">📢 <b>%s:</b></font> <font color="rgb(255,255,200)">%s</font>',
		sanitize(sender), sanitize(text)
	)

	trimMessages()
	task.defer(function()
		ChatScroll.CanvasPosition = Vector2.new(0, ChatScroll.AbsoluteCanvasSize.Y)
	end)

	task.delay(6, function()
		if toast and toast.Parent then toast:Destroy() end
	end)
end

local function SendToFirebase(sender, message, msgId)
	safeRequest({
		Url = FIREBASE_URL .. CHAT_PATH .. ".json",
		Method = "POST",
		Headers = {["Content-Type"] = "application/json"},
		Body = HttpService:JSONEncode({
			["Sender"] = sender,
			["Message"] = message,
			["Time"] = os.time(),
			["MsgId"] = msgId
		})
	})
end

local seenKeys = {}

-- 7. ANNOUNCEMENT LOGIC
local seenAnnouncements = {}

local function SeedAnnouncements()
	local response = safeRequest({
		Url = FIREBASE_URL .. ANNOUNCE_PATH .. ".json",
		Method = "GET"
	})
	if response.Success and response.Body ~= "null" then
		local ok, data = pcall(HttpService.JSONDecode, HttpService, response.Body)
		if ok then
			for key, _ in pairs(data) do
				seenAnnouncements[key] = true
			end
		end
	end
end

local function CheckAnnouncement()
	local response = safeRequest({
		Url = FIREBASE_URL .. ANNOUNCE_PATH .. ".json",
		Method = "GET"
	})
	if not response.Success or response.Body == "null" then return end
	local ok, data = pcall(HttpService.JSONDecode, HttpService, response.Body)
	if not ok then return end

	local entries = {}
	for key, v in pairs(data) do
		if not seenAnnouncements[key] then
			table.insert(entries, {key = key, data = v})
		end
	end

	table.sort(entries, function(a, b) return a.data.PostedAt < b.data.PostedAt end)

	for _, entry in ipairs(entries) do
		seenAnnouncements[entry.key] = true
		CreateAnnouncement(entry.data.PostedBy or "Owner", entry.data.Message or "")
	end
end

local function ShowBubble(senderName, text)
	local target = nil
	for _, p in ipairs(Players:GetPlayers()) do
		if p.DisplayName == senderName or p.Name == senderName then
			target = p
			break
		end
	end
	if not target or not target.Character then return end
	local head = target.Character:FindFirstChild("Head")
	if not head then return end

	local existing = head:FindFirstChild("BetterChatBubble")
	if existing then existing:Destroy() end

	local billboard = Instance.new("BillboardGui", head)
	billboard.Name = "BetterChatBubble"
	billboard.Size = UDim2.new(0, 200, 0, 50)
	billboard.StudsOffset = Vector3.new(0, 2.5, 0)
	billboard.AlwaysOnTop = false
	billboard.ResetOnSpawn = false

	local bg = Instance.new("Frame", billboard)
	bg.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	bg.BackgroundTransparency = 0.1
	bg.Size = UDim2.new(1, 0, 1, 0)
	bg.BorderSizePixel = 0
	Instance.new("UICorner", bg).CornerRadius = UDim.new(0, 8)

	local label = Instance.new("TextLabel", bg)
	label.BackgroundTransparency = 1
	label.Size = UDim2.new(1, -10, 1, 0)
	label.Position = UDim2.new(0, 5, 0, 0)
	label.Font = Enum.Font.GothamMedium
	label.TextSize = 13
	label.TextColor3 = Color3.fromRGB(0, 0, 0)
	label.TextWrapped = true
	label.Text = text
	label.TextXAlignment = Enum.TextXAlignment.Center

	billboard.Size = UDim2.new(0, 200, 0, math.clamp(label.TextBounds.Y + 16, 36, 80))

	task.delay(5, function()
		if billboard and billboard.Parent then
			billboard:Destroy()
		end
	end)
end

-- FIX 2: StartPolling Optimized for Instant Chat
local function StartPolling()
	task.spawn(function()
		while ScreenGui.Parent and task.wait(2.5) do
			local response = safeRequest({
				Url = FIREBASE_URL .. CHAT_PATH .. ".json?orderBy=\"Time\"&limitToLast=10",
				Method = "GET"
			})
			if response.Success and response.Body ~= "null" then
				local ok, data = pcall(HttpService.JSONDecode, HttpService, response.Body)
				if not ok then continue end
				local msgs = {}
				for key, v in pairs(data) do
					local uid = v.MsgId or key -- استخدام المعرف الفريد إن وجد
					if not seenKeys[uid] then
						table.insert(msgs, {uid = uid, data = v})
					end
				end
				table.sort(msgs, function(a, b) return a.data.Time < b.data.Time end)
				for _, entry in ipairs(msgs) do
					seenKeys[entry.uid] = true
					CreateMessage(entry.data.Sender, entry.data.Message, false)
					ShowBubble(entry.data.Sender, entry.data.Message)
				end
			end
		end
	end)

	task.spawn(function()
		while ScreenGui.Parent and task.wait(5) do
			CheckAnnouncement()
		end
	end)
end

-- 8. ONLINE PLAYERS LOGIC

local function ClearOnlineList()
	for _, c in ipairs(OnlineScroll:GetChildren()) do
		if not c:IsA("UIListLayout") then c:Destroy() end
	end
end

local function BuildPlayerRow(info)
	local row = Instance.new("Frame", OnlineScroll)
	row.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	row.BackgroundTransparency = 0.9
	row.Size = UDim2.new(1, 0, 0, 32)
	Instance.new("UICorner", row)

	local nameLabel = Instance.new("TextLabel", row)
	nameLabel.BackgroundTransparency = 1
	nameLabel.Position = UDim2.new(0, 8, 0, 0)
	nameLabel.Size = UDim2.new(1, -90, 1, 0)
	nameLabel.Font = Enum.Font.GothamMedium
	nameLabel.TextSize = 13
	nameLabel.TextColor3 = Color3.fromRGB(220, 220, 220)
	nameLabel.TextXAlignment = Enum.TextXAlignment.Left
	nameLabel.TextTruncate = Enum.TextTruncate.AtEnd
	nameLabel.RichText = true

	local sameServer = info.JobId == MY_JOB_ID
	local tag = sameServer and " <font color='rgb(100,255,150)'>[Here]</font>" or ""
	nameLabel.Text = sanitize(info.DisplayName) .. " (@" .. sanitize(info.Username) .. ")" .. tag

	if not sameServer then
		local joinBtn = Instance.new("TextButton", row)
		joinBtn.BackgroundColor3 = Color3.fromRGB(0, 180, 100)
		joinBtn.BackgroundTransparency = 0.3
		joinBtn.Position = UDim2.new(1, -82, 0.5, -12)
		joinBtn.Size = UDim2.new(0, 74, 0, 24)
		joinBtn.Font = Enum.Font.GothamBold
		joinBtn.Text = "Join"
		joinBtn.TextColor3 = Color3.fromRGB(255, 255, 255)
		joinBtn.TextSize = 12
		Instance.new("UICorner", joinBtn).CornerRadius = UDim.new(0, 6)

		joinBtn.MouseButton1Click:Connect(function()
			pcall(function()
				TeleportService:TeleportToPlaceInstance(info.PlaceId, info.JobId, LocalPlayer)
			end)
		end)
	end
end

local function RefreshOnlinePlayers()
	local response = safeRequest({
		Url = FIREBASE_URL .. PRESENCE_PATH .. ".json",
		Method = "GET"
	})
	ClearOnlineList()
	if not response.Success or response.Body == "null" then
		local empty = Instance.new("TextLabel", OnlineScroll)
		empty.BackgroundTransparency = 1
		empty.Size = UDim2.new(1, 0, 0, 24)
		empty.Font = Enum.Font.Gotham
		empty.Text = "No players online."
		empty.TextColor3 = Color3.fromRGB(160, 160, 160)
		empty.TextSize = 13
		return
	end

	local ok, data = pcall(HttpService.JSONDecode, HttpService, response.Body)
	if not ok then return end

	local now = os.time()
	for _, info in pairs(data) do
		if (now - (info.LastSeen or 0)) <= PRESENCE_TTL then
			BuildPlayerRow(info)
		end
	end
end

task.spawn(function()
	while ScreenGui.Parent and task.wait(15) do
		if OnlinePanel.Visible then RefreshOnlinePlayers() end
	end
end)

-- 9. COMMAND SYSTEM
local flying = false
local flyBody = nil

local function StopFly()
	flying = false
	if flyBody then flyBody:Destroy() flyBody = nil end
	local char = LocalPlayer.Character
	if char then
		local hum = char:FindFirstChildOfClass("Humanoid")
		if hum then hum.PlatformStand = false end
	end
end

local function StartFly()
	local char = LocalPlayer.Character
	if not char then return end
	local hum = char:FindFirstChildOfClass("Humanoid")
	local root = char:FindFirstChild("HumanoidRootPart")
	if not hum or not root then return end

	flying = true
	hum.PlatformStand = true

	local bg = Instance.new("BodyGyro", root)
	bg.MaxTorque = Vector3.new(1e6, 1e6, 1e6)
	bg.P = 1e6
	flyBody = bg

	local bv = Instance.new("BodyVelocity", root)
	bv.MaxForce = Vector3.new(1e6, 1e6, 1e6)
	bv.Velocity = Vector3.zero

	local cam = workspace.CurrentCamera
	local SPEED = 60

	task.spawn(function()
		while flying and root.Parent do
			local cf = cam.CFrame
			local dir = Vector3.zero
			if UserInputService:IsKeyDown(Enum.KeyCode.W) then dir += cf.LookVector end
			if UserInputService:IsKeyDown(Enum.KeyCode.S) then dir -= cf.LookVector end
			if UserInputService:IsKeyDown(Enum.KeyCode.A) then dir -= cf.RightVector end
			if UserInputService:IsKeyDown(Enum.KeyCode.D) then dir += cf.RightVector end
			if UserInputService:IsKeyDown(Enum.KeyCode.Space) then dir += Vector3.new(0,1,0) end
			if UserInputService:IsKeyDown(Enum.KeyCode.LeftShift) then dir -= Vector3.new(0,1,0) end
			bv.Velocity = dir.Magnitude > 0 and dir.Unit * SPEED or Vector3.zero
			bg.CFrame = cf
			task.wait()
		end
		bv:Destroy()
	end)
end

local function PlayDance()
	local char = LocalPlayer.Character
	if not char then return end
	local hum = char:FindFirstChildOfClass("Humanoid")
	if not hum then return end

	local animId = hum.RigType == Enum.HumanoidRigType.R15
		and "rbxassetid://507771019"
		or  "rbxassetid://182435998"

	local anim = Instance.new("Animation")
	anim.AnimationId = animId
	local track = hum:LoadAnimation(anim)
	track:Play()
	CreateMessage("System", "Dancing!", true)
end

local HELP_TEXT = {
	"━━━ BetterChat Commands ━━━",
	"/help — shows this list",
	"/clear — clears the chat window",
	"/e dance — makes your character dance",
	"/console — opens the developer console",
	"/fly — toggle flying (WASD + Space/Shift)",
	"/speed [n] — sets your walk speed",
	"/jump [n] — sets your jump power",
	"/leave — exits the game",
	"━━━━━━━━━━━━━━━━━━━━━━━━━━",
}

local function HandleCommand(input)
	local args = {}
	for word in input:gmatch("%S+") do table.insert(args, word) end
	local cmd = args[1] and args[1]:lower()

	if cmd == "/help" then
		for _, line in ipairs(HELP_TEXT) do CreateMessage("System", line, true) end
	elseif cmd == "/clear" then
		for _, c in ipairs(ChatScroll:GetChildren()) do
			if c:IsA("TextLabel") then c:Destroy() end
		end
		CreateMessage("System", "Chat cleared.", true)
	elseif cmd == "/e" and args[2] and args[2]:lower() == "dance" then
		PlayDance()
	elseif cmd == "/console" then
		StarterGui:SetCore("DevConsoleVisible", true)
	elseif cmd == "/fly" then
		if flying then
			StopFly()
			CreateMessage("System", "Fly disabled.", true)
		else
			StartFly()
			CreateMessage("System", "Fly enabled. WASD to move, Space/Shift for up/down.", true)
		end
	elseif cmd == "/speed" then
		local val = tonumber(args[2])
		if not val then return true end
		local char = LocalPlayer.Character
		local hum = char and char:FindFirstChildOfClass("Humanoid")
		if hum then hum.WalkSpeed = val CreateMessage("System", "Walk speed set to " .. val, true) end
	elseif cmd == "/jump" then
		local val = tonumber(args[2])
		if not val then return true end
		local char = LocalPlayer.Character
		local hum = char and char:FindFirstChildOfClass("Humanoid")
		if hum then hum.JumpPower = val hum.JumpHeight = val CreateMessage("System", "Jump power set to " .. val, true) end
	elseif cmd == "/leave" then
		TeleportService:Teleport(game.PlaceId)
	else
		return false
	end
	return true
end

-- 10. FIRST RUN TUTORIAL
local function ShowTutorial()
	local overlay = Instance.new("Frame", ScreenGui)
	overlay.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
	overlay.BackgroundTransparency = 0.4
	overlay.Size = UDim2.new(1, 0, 1, 0)
	overlay.ZIndex = 10

	local card = Instance.new("Frame", overlay)
	card.BackgroundColor3 = Color3.fromRGB(15, 15, 15)
	card.BackgroundTransparency = 0.1
	card.AnchorPoint = Vector2.new(0.5, 0.5)
	card.Position = UDim2.new(0.5, 0, 0.5, 0)
	card.Size = UDim2.new(0, 340, 0, 220)
	card.ZIndex = 11
	Instance.new("UICorner", card).CornerRadius = UDim.new(0, 12)

	local titleLabel = Instance.new("TextLabel", card)
	titleLabel.BackgroundTransparency = 1
	titleLabel.Position = UDim2.new(0, 0, 0, 16)
	titleLabel.Size = UDim2.new(1, 0, 0, 28)
	titleLabel.Font = Enum.Font.GothamBold
	titleLabel.TextColor3 = Color3.fromRGB(100, 200, 255)
	titleLabel.TextSize = 18
	titleLabel.TextXAlignment = Enum.TextXAlignment.Center
	titleLabel.ZIndex = 12

	local bodyLabel = Instance.new("TextLabel", card)
	bodyLabel.BackgroundTransparency = 1
	bodyLabel.Position = UDim2.new(0, 24, 0, 58)
	bodyLabel.Size = UDim2.new(1, -48, 0, 110)
	bodyLabel.Font = Enum.Font.GothamMedium
	bodyLabel.TextColor3 = Color3.fromRGB(220, 220, 220)
	bodyLabel.TextSize = 14
	bodyLabel.TextWrapped = true
	bodyLabel.TextXAlignment = Enum.TextXAlignment.Center
	bodyLabel.TextYAlignment = Enum.TextYAlignment.Top
	bodyLabel.ZIndex = 12

	local pageLabel = Instance.new("TextLabel", card)
	pageLabel.BackgroundTransparency = 1
	pageLabel.Position = UDim2.new(0, 16, 1, -36)
	pageLabel.Size = UDim2.new(0.4, 0, 0, 30)
	pageLabel.Font = Enum.Font.Gotham
	pageLabel.TextColor3 = Color3.fromRGB(120, 120, 120)
	pageLabel.TextSize = 12
	pageLabel.TextXAlignment = Enum.TextXAlignment.Left
	pageLabel.ZIndex = 12

	local nextBtn = Instance.new("TextButton", card)
	nextBtn.BackgroundColor3 = Color3.fromRGB(0, 150, 255)
	nextBtn.AnchorPoint = Vector2.new(1, 1)
	nextBtn.Position = UDim2.new(1, -16, 1, -16)
	nextBtn.Size = UDim2.new(0, 100, 0, 30)
	nextBtn.Font = Enum.Font.GothamBold
	nextBtn.TextColor3 = Color3.fromRGB(255, 255, 255)
	nextBtn.TextSize = 14
	nextBtn.ZIndex = 12
	Instance.new("UICorner", nextBtn).CornerRadius = UDim.new(0, 8)

	local pages = {
		{
			title = "👋 Welcome to BetterChat",
			body = "BetterChat replaces Roblox's default chat with a global cross-server chat. You can talk to anyone running the script, no matter what server they're in."
		},
		{
			title = "💬 How to Chat",
			body = "Type your message in the box at the bottom of the chat window and press Enter to send.\n\nPress / anywhere in-game to quickly focus the chat box."
		},
		{
			title = "👁 Toggling the Chat",
			body = "Click the chat icon in the top-right to show or hide the chat window.\n\nA red badge will appear showing how many new messages you've missed while it's hidden."
		},
		{
			title = "👥 Joining Other Players",
			body = "Click the green 👥 button to open the Online Players panel.\n\nAnyone else running BetterChat appears here. Press Join to teleport directly into their server."
		},
		{
			title = "⌨️ Commands",
			body = "Type /help in chat to see all available commands.\n\nCommands include /fly, /speed, /jump, /clear, /leave, /e dance, and more."
		},
		{
			title = "✅ You're all set!",
			body = "BetterChat is now active. This tutorial won't show again.\n\nEnjoy chatting!"
		}
	}

	local currentPage = 1

	local function ShowPage(n)
		local p = pages[n]
		titleLabel.Text = p.title
		bodyLabel.Text = p.body
		pageLabel.Text = n .. " / " .. #pages
		nextBtn.Text = n == #pages and "Done ✓" or "Next →"
	end

	ShowPage(1)

	nextBtn.MouseButton1Click:Connect(function()
		if currentPage < #pages then
			currentPage += 1
			ShowPage(currentPage)
		else
			overlay:Destroy()
		end
	end)
end

-- 11. CONNECTIONS

ToggleButton.MouseButton1Click:Connect(function()
	Container.Visible = not Container.Visible
	if Container.Visible then ClearUnread() end
end)

OnlineButton.MouseButton1Click:Connect(function()
	OnlinePanel.Visible = not OnlinePanel.Visible
	if OnlinePanel.Visible then RefreshOnlinePlayers() end
end)

TextBox.FocusLost:Connect(function(enter)
	if enter and TextBox.Text ~= "" then
		local input = TextBox.Text
		TextBox.Text = ""
		if not HandleCommand(input) then
			local dName = LocalPlayer.DisplayName
			local msgId = HttpService:GenerateGUID(false)
			seenKeys[msgId] = true

			-- إظهار الرسالة للمستخدم في اللحظة نفسها (Optimistic UI)
			CreateMessage(dName, input, false)
			ShowBubble(dName, input)

			-- رفع الرسالة للسيرفر بالخلفية
			task.spawn(function()
				SendToFirebase(dName, input, msgId)
			end)
		end
	end
end)

UserInputService.InputBegan:Connect(function(i, g)
	if i.KeyCode == Enum.KeyCode.Slash and not g then
		if not Container.Visible then Container.Visible = true end
		task.wait()
		TextBox:CaptureFocus()
	end
end)

SeedAnnouncements()
StartPolling()
CreateMessage("System", "BetterChat V" .. CURRENT_VERSION_STRING .. " loaded. Type /help for commands.", true)

if isFirstRun then
	ShowTutorial()
end

game:GetService("Players").PlayerAdded:Connect(function(player)
    if typeof(AddMessage) == "function" then
        AddMessage("System", player.Name .. " joined the chat", Color3.fromRGB(255, 255, 255))
    end
end)

Features & Information

Blook

Community Comments (0)

No comments yet for this script.

More Scripts for Universal Scripts